@@ -20,15 +20,13 @@ export const dynamic = "force-dynamic";
2020export const revalidate = 0 ;
2121
2222const DATABRICKS_CACHE_REVALIDATE_SECONDS = 12 * 60 * 60 ;
23- const BROWSER_CACHE_SECONDS = 60 ;
2423const EDGE_STALE_SECONDS = 24 * 60 * 60 ;
2524const DEFAULT_RANGE_DAYS = 90 ;
25+ const MAX_RANGE_DAYS = Math . max ( ...rangeOptions . map ( ( option ) => option . value ) ) ;
26+ const DATABRICKS_CACHE_KEY_VERSION = "solana-data-databricks-metric-rows-v4" ;
2627const IS_PRODUCTION = isProduction ( ) ;
2728const NO_STORE_CACHE_CONTROL = "no-store, max-age=0" ;
2829const DAY_IN_MS = 24 * 60 * 60 * 1000 ;
29- const DATA_REFRESH_TIME_ZONE = "America/New_York" ;
30- // Source jobs run around 9 AM and 9 PM Eastern; roll dashboard caches an hour later.
31- const DATA_REFRESH_HOURS = [ 10 , 22 ] as const ;
3230const DATA_UNAVAILABLE_ERROR =
3331 "Solana data is unavailable right now. Try again in a moment." ;
3432
@@ -56,7 +54,7 @@ export async function GET(request: NextRequest) {
5654 const rangeDays = parseRangeDays ( request . nextUrl . searchParams . get ( "days" ) ) ;
5755
5856 try {
59- const result = await getDatabricksData ( configResult . config , rangeDays ) ;
57+ const result = await getDatabricksData ( configResult . config ) ;
6058
6159 return json < DataApiResponse > (
6260 buildDataResponse ( result , rangeDays ) ,
@@ -105,33 +103,29 @@ function filterRowsByRange(rows: MetricRow[], rangeDays: number) {
105103 } ) ;
106104}
107105
108- function getDatabricksData ( config : DatabricksConfig , rangeDays : number ) {
106+ function getDatabricksData ( config : DatabricksConfig ) {
109107 return IS_PRODUCTION
110- ? getCachedDatabricksData ( config , rangeDays )
111- : fetchDatabricksData ( config , rangeDays ) ;
108+ ? getCachedDatabricksData ( config )
109+ : fetchDatabricksData ( config ) ;
112110}
113111
114- function getCachedDatabricksData ( config : DatabricksConfig , rangeDays : number ) {
112+ function getCachedDatabricksData ( config : DatabricksConfig ) {
113+ const dataCacheKey = getDatabricksCacheKey ( config ) ;
114+ const cacheKeyParts = [ DATABRICKS_CACHE_KEY_VERSION , dataCacheKey ] ;
115+
115116 return unstable_cache (
116- ( ) => fetchDatabricksData ( config , rangeDays ) ,
117- [
118- "solana-data-databricks-metric-rows-v3" ,
119- getDatabricksRefreshCacheKey ( ) ,
120- getDatabricksCacheKey ( config , rangeDays ) ,
121- ] ,
117+ ( ) => getInMemoryCachedDatabricksData ( config , cacheKeyParts . join ( "|" ) ) ,
118+ cacheKeyParts ,
122119 {
123120 revalidate : DATABRICKS_CACHE_REVALIDATE_SECONDS ,
124121 tags : [ "solana-data-databricks" ] ,
125122 } ,
126123 ) ( ) ;
127124}
128125
129- async function fetchDatabricksData (
130- config : DatabricksConfig ,
131- rangeDays : number ,
132- ) {
126+ async function fetchDatabricksData ( config : DatabricksConfig ) {
133127 const result = await getDatabricksSqlMetricRows ( config , {
134- lookbackDays : rangeDays ,
128+ lookbackDays : MAX_RANGE_DAYS ,
135129 metricNames,
136130 } ) ;
137131
@@ -141,139 +135,64 @@ async function fetchDatabricksData(
141135 } ;
142136}
143137
144- function getDatabricksCacheKey ( config : DatabricksConfig , rangeDays : number ) {
145- return [
146- config . serverHostname ,
147- config . httpPath ,
148- config . warehouseId ,
149- rangeDays ,
150- metricNames . join ( "," ) ,
151- ] . join ( "|" ) ;
152- }
153-
154- function getDatabricksRefreshCacheKey ( now = new Date ( ) ) {
155- const parts = getTimeZoneDateTimeParts ( now , DATA_REFRESH_TIME_ZONE ) ;
156- const latestRefreshHour = getLatestScheduledRefreshHour ( parts . hour ) ;
157- const refreshHour =
158- latestRefreshHour ?? DATA_REFRESH_HOURS [ DATA_REFRESH_HOURS . length - 1 ] ;
159- const refreshDate = new Date (
160- Date . UTC (
161- parts . year ,
162- parts . month - 1 ,
163- parts . day + ( latestRefreshHour === undefined ? - 1 : 0 ) ,
164- refreshHour ,
165- ) ,
166- ) ;
138+ const databricksDataRequests = new Map <
139+ string ,
140+ Promise < Awaited < ReturnType < typeof fetchDatabricksData > > >
141+ > ( ) ;
167142
168- return `${ refreshDate . toISOString ( ) . slice ( 0 , 10 ) } -${ String ( refreshHour ) . padStart ( 2 , "0" ) } ` ;
169- }
143+ function getInMemoryCachedDatabricksData (
144+ config : DatabricksConfig ,
145+ cacheKey : string ,
146+ ) {
147+ const cachedRequest = databricksDataRequests . get ( cacheKey ) ;
170148
171- function getSuccessCacheControl ( now = new Date ( ) ) {
172- if ( ! IS_PRODUCTION ) {
173- return NO_STORE_CACHE_CONTROL ;
149+ if ( cachedRequest ) {
150+ return cachedRequest ;
174151 }
175152
176- return [
177- "public" ,
178- `max-age=${ BROWSER_CACHE_SECONDS } ` ,
179- `s-maxage=${ getSecondsUntilNextScheduledRefresh ( now ) } ` ,
180- `stale-while-revalidate=${ EDGE_STALE_SECONDS } ` ,
181- ] . join ( ", " ) ;
182- }
153+ pruneDatabricksDataRequests ( cacheKey ) ;
183154
184- function getSecondsUntilNextScheduledRefresh ( now = new Date ( ) ) {
185- const parts = getTimeZoneDateTimeParts ( now , DATA_REFRESH_TIME_ZONE ) ;
186- const nextRefresh = getScheduledRefreshDate ( parts , now ) ;
187- const seconds = Math . ceil ( ( nextRefresh . getTime ( ) - now . getTime ( ) ) / 1000 ) ;
155+ const request = fetchDatabricksData ( config ) ;
188156
189- return Math . max ( 1 , seconds ) ;
190- }
191-
192- function getScheduledRefreshDate (
193- parts : ReturnType < typeof getTimeZoneDateTimeParts > ,
194- now : Date ,
195- ) {
196- const nextRefreshHour = getNextScheduledRefreshHour ( parts . hour ) ;
197- const refreshHour = nextRefreshHour ?? DATA_REFRESH_HOURS [ 0 ] ;
198- const dayOffset = nextRefreshHour === undefined ? 1 : 0 ;
199-
200- return zonedTimeToUtc (
201- parts . year ,
202- parts . month ,
203- parts . day + dayOffset ,
204- refreshHour ,
205- DATA_REFRESH_TIME_ZONE ,
206- now ,
157+ request . then (
158+ ( ) => databricksDataRequests . delete ( cacheKey ) ,
159+ ( ) => databricksDataRequests . delete ( cacheKey ) ,
207160 ) ;
208- }
209161
210- function getLatestScheduledRefreshHour ( hour : number ) {
211- return DATA_REFRESH_HOURS . findLast ( ( refreshHour ) => hour >= refreshHour ) ;
212- }
162+ databricksDataRequests . set ( cacheKey , request ) ;
213163
214- function getNextScheduledRefreshHour ( hour : number ) {
215- return DATA_REFRESH_HOURS . find ( ( refreshHour ) => hour < refreshHour ) ;
164+ return request ;
216165}
217166
218- function zonedTimeToUtc (
219- year : number ,
220- month : number ,
221- day : number ,
222- hour : number ,
223- timeZone : string ,
224- referenceDate : Date ,
225- ) {
226- const utcGuess = new Date ( Date . UTC ( year , month - 1 , day , hour ) ) ;
227- const firstPass = new Date (
228- utcGuess . getTime ( ) - getTimeZoneOffsetMs ( utcGuess , timeZone ) ,
229- ) ;
230- const secondPass = new Date (
231- utcGuess . getTime ( ) - getTimeZoneOffsetMs ( firstPass , timeZone ) ,
232- ) ;
233-
234- return Math . abs ( secondPass . getTime ( ) - referenceDate . getTime ( ) ) <
235- DAY_IN_MS * 2
236- ? secondPass
237- : firstPass ;
167+ function pruneDatabricksDataRequests ( activeCacheKey : string ) {
168+ for ( const cacheKey of databricksDataRequests . keys ( ) ) {
169+ if ( cacheKey !== activeCacheKey ) {
170+ databricksDataRequests . delete ( cacheKey ) ;
171+ }
172+ }
238173}
239174
240- function getTimeZoneOffsetMs ( date : Date , timeZone : string ) {
241- const parts = getTimeZoneDateTimeParts ( date , timeZone ) ;
242- const zonedAsUtc = Date . UTC (
243- parts . year ,
244- parts . month - 1 ,
245- parts . day ,
246- parts . hour ,
247- parts . minute ,
248- parts . second ,
249- ) ;
250-
251- return zonedAsUtc - date . getTime ( ) ;
175+ function getDatabricksCacheKey ( config : DatabricksConfig ) {
176+ return [
177+ config . serverHostname ,
178+ config . httpPath ,
179+ config . warehouseId ,
180+ MAX_RANGE_DAYS ,
181+ metricNames . join ( "," ) ,
182+ ] . join ( "|" ) ;
252183}
253184
254- function getTimeZoneDateTimeParts ( date : Date , timeZone : string ) {
255- const parts = new Intl . DateTimeFormat ( "en-US" , {
256- day : "2-digit" ,
257- hour : "2-digit" ,
258- hourCycle : "h23" ,
259- minute : "2-digit" ,
260- month : "2-digit" ,
261- second : "2-digit" ,
262- timeZone,
263- year : "numeric" ,
264- } ) . formatToParts ( date ) ;
265- const valueByType = new Map (
266- parts . map ( ( part ) => [ part . type , Number ( part . value ) ] ) ,
267- ) ;
185+ function getSuccessCacheControl ( ) {
186+ if ( ! IS_PRODUCTION ) {
187+ return NO_STORE_CACHE_CONTROL ;
188+ }
268189
269- return {
270- day : valueByType . get ( "day" ) ?? 1 ,
271- hour : valueByType . get ( "hour" ) ?? 0 ,
272- minute : valueByType . get ( "minute" ) ?? 0 ,
273- month : valueByType . get ( "month" ) ?? 1 ,
274- second : valueByType . get ( "second" ) ?? 0 ,
275- year : valueByType . get ( "year" ) ?? 1970 ,
276- } ;
190+ return [
191+ "public" ,
192+ "max-age=0" ,
193+ `s-maxage=${ DATABRICKS_CACHE_REVALIDATE_SECONDS } ` ,
194+ `stale-while-revalidate=${ EDGE_STALE_SECONDS } ` ,
195+ ] . join ( ", " ) ;
277196}
278197
279198function getConfigErrorResponse (
0 commit comments