stat-sync.ts 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. import { DateTime, Effect } from "effect"
  2. import { Resource } from "sst/resource"
  3. import { Athena, AthenaQueryError, AthenaQueryTimeoutError } from "./athena"
  4. import { DatabaseError } from "./database"
  5. import { GeoStatRepo, rowsFromAggregates as geoRowsFromAggregates } from "./domain/geo"
  6. import { buildStatsQuery, toGeoAggregate, toModelAggregate, toProviderAggregate } from "./domain/inference"
  7. import { ModelStatRepo, rowsFromAggregates as modelRowsFromAggregates } from "./domain/model"
  8. import { ProviderStatRepo, rowsFromAggregates as providerRowsFromAggregates } from "./domain/provider"
  9. import { startOfIsoWeek } from "./domain/stat"
  10. const DATALAKE_INGESTION_LAG_MS = 5 * 60_000
  11. const STATS_DATA_START_MS = new Date("2026-05-28T00:00:00.000Z").getTime()
  12. const WEEK_MS = 7 * 86_400_000
  13. const DISPLAY_WINDOW_MS = 56 * 86_400_000
  14. export type SyncStatsResult = { ok: true; rows: number; startedAt: string; periodStart: string; periodEnd: string }
  15. export type SyncStatsError = AthenaQueryError | AthenaQueryTimeoutError | DatabaseError
  16. export const syncStats: () => Effect.Effect<
  17. SyncStatsResult,
  18. SyncStatsError,
  19. Athena | ModelStatRepo | ProviderStatRepo | GeoStatRepo
  20. > = Effect.fn("StatSync.sync")(function* () {
  21. const startedAt = yield* DateTime.nowAsDate
  22. const periodEnd = new Date(Math.floor((startedAt.getTime() - DATALAKE_INGESTION_LAG_MS) / 60_000) * 60_000)
  23. // May 27 was partial, so keep Athena stats anchored at the first complete day.
  24. const periodStart = new Date(
  25. Math.max(
  26. Math.min(startOfIsoWeek(periodEnd).getTime() - WEEK_MS, periodEnd.getTime() - DISPLAY_WINDOW_MS),
  27. STATS_DATA_START_MS,
  28. ),
  29. )
  30. const athena = yield* Athena
  31. const modelStats = yield* ModelStatRepo
  32. const providerStats = yield* ProviderStatRepo
  33. const geoStats = yield* GeoStatRepo
  34. yield* logRuntimeCheck()
  35. const [modelAggregates, providerAggregates, geoAggregates, geoModelAggregates] = yield* Effect.all(
  36. [
  37. athena
  38. .query(buildStatsQuery(periodStart, periodEnd, "model"))
  39. .pipe(Effect.map((rows) => rows.flatMap(toModelAggregate))),
  40. athena
  41. .query(buildStatsQuery(periodStart, periodEnd, "provider"))
  42. .pipe(Effect.map((rows) => rows.flatMap(toProviderAggregate))),
  43. athena
  44. .query(buildStatsQuery(periodStart, periodEnd, "geo"))
  45. .pipe(Effect.map((rows) => rows.flatMap(toGeoAggregate))),
  46. athena
  47. .query(buildStatsQuery(periodStart, periodEnd, "geo_model"))
  48. .pipe(Effect.map((rows) => rows.flatMap(toGeoAggregate))),
  49. ],
  50. { concurrency: "unbounded" },
  51. )
  52. const modelRows = modelRowsFromAggregates(modelAggregates)
  53. const providerRows = providerRowsFromAggregates(providerAggregates)
  54. const geoRows = geoRowsFromAggregates([...geoAggregates, ...geoModelAggregates])
  55. yield* Effect.all([modelStats.upsert(modelRows), providerStats.upsert(providerRows), geoStats.upsert(geoRows)], {
  56. concurrency: "unbounded",
  57. discard: true,
  58. })
  59. yield* Effect.all(
  60. [
  61. modelStats.deleteRetiredDimensions(modelRows),
  62. providerStats.deleteRetiredDimensions(providerRows),
  63. geoStats.deleteRetiredDimensions(geoRows),
  64. ],
  65. { concurrency: "unbounded", discard: true },
  66. )
  67. yield* Effect.logInfo(
  68. `stats sync complete ${JSON.stringify({
  69. startedAt: startedAt.toISOString(),
  70. periodStart: periodStart.toISOString(),
  71. periodEnd: periodEnd.toISOString(),
  72. rows: modelRows.length,
  73. providerRows: providerRows.length,
  74. geoRows: geoRows.length,
  75. stage: Resource.App.stage,
  76. })}`,
  77. )
  78. return {
  79. ok: true,
  80. rows: modelRows.length,
  81. startedAt: startedAt.toISOString(),
  82. periodStart: periodStart.toISOString(),
  83. periodEnd: periodEnd.toISOString(),
  84. }
  85. })
  86. function logRuntimeCheck() {
  87. return Effect.logInfo(
  88. `athena stats runtime check ${JSON.stringify({
  89. catalog: Resource.InferenceEvent.catalog,
  90. database: Resource.InferenceEvent.database,
  91. dataset: Resource.StatsSyncConfig.dataset,
  92. table: Resource.InferenceEvent.table,
  93. workgroup: Resource.InferenceEvent.workgroup,
  94. region: Resource.InferenceEvent.region,
  95. stage: Resource.App.stage,
  96. })}`,
  97. )
  98. }