stat-sync.ts 3.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  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 WEEK_MS = 7 * 86_400_000
  12. export type SyncStatsResult = { ok: true; rows: number; startedAt: string; periodStart: string; periodEnd: string }
  13. export type SyncStatsError = AthenaQueryError | AthenaQueryTimeoutError | DatabaseError
  14. export const syncStats: () => Effect.Effect<
  15. SyncStatsResult,
  16. SyncStatsError,
  17. Athena | ModelStatRepo | ProviderStatRepo | GeoStatRepo
  18. > = Effect.fn("StatSync.sync")(function* () {
  19. const startedAt = yield* DateTime.nowAsDate
  20. const periodEnd = new Date(Math.floor((startedAt.getTime() - DATALAKE_INGESTION_LAG_MS) / 60_000) * 60_000)
  21. const periodStart = new Date(startOfIsoWeek(periodEnd).getTime() - WEEK_MS)
  22. const athena = yield* Athena
  23. const modelStats = yield* ModelStatRepo
  24. const providerStats = yield* ProviderStatRepo
  25. const geoStats = yield* GeoStatRepo
  26. yield* logRuntimeCheck()
  27. const [modelAggregates, providerAggregates, geoAggregates, geoModelAggregates] = yield* Effect.all(
  28. [
  29. athena
  30. .query(buildStatsQuery(periodStart, periodEnd, "model"))
  31. .pipe(Effect.map((rows) => rows.flatMap(toModelAggregate))),
  32. athena
  33. .query(buildStatsQuery(periodStart, periodEnd, "provider"))
  34. .pipe(Effect.map((rows) => rows.flatMap(toProviderAggregate))),
  35. athena
  36. .query(buildStatsQuery(periodStart, periodEnd, "geo"))
  37. .pipe(Effect.map((rows) => rows.flatMap(toGeoAggregate))),
  38. athena
  39. .query(buildStatsQuery(periodStart, periodEnd, "geo_model"))
  40. .pipe(Effect.map((rows) => rows.flatMap(toGeoAggregate))),
  41. ],
  42. { concurrency: "unbounded" },
  43. )
  44. const modelRows = modelRowsFromAggregates(modelAggregates)
  45. const providerRows = providerRowsFromAggregates(providerAggregates)
  46. const geoRows = geoRowsFromAggregates([...geoAggregates, ...geoModelAggregates])
  47. yield* Effect.all([modelStats.upsert(modelRows), providerStats.upsert(providerRows), geoStats.upsert(geoRows)], {
  48. concurrency: "unbounded",
  49. discard: true,
  50. })
  51. yield* Effect.logInfo(
  52. `stats sync complete ${JSON.stringify({
  53. startedAt: startedAt.toISOString(),
  54. periodStart: periodStart.toISOString(),
  55. periodEnd: periodEnd.toISOString(),
  56. rows: modelRows.length,
  57. providerRows: providerRows.length,
  58. geoRows: geoRows.length,
  59. stage: Resource.App.stage,
  60. })}`,
  61. )
  62. return {
  63. ok: true,
  64. rows: modelRows.length,
  65. startedAt: startedAt.toISOString(),
  66. periodStart: periodStart.toISOString(),
  67. periodEnd: periodEnd.toISOString(),
  68. }
  69. })
  70. function logRuntimeCheck() {
  71. return Effect.logInfo(
  72. `athena stats runtime check ${JSON.stringify({
  73. catalog: Resource.InferenceEvent.catalog,
  74. database: Resource.InferenceEvent.database,
  75. dataset: Resource.StatsSyncConfig.dataset,
  76. table: Resource.InferenceEvent.table,
  77. workgroup: Resource.InferenceEvent.workgroup,
  78. region: Resource.InferenceEvent.region,
  79. stage: Resource.App.stage,
  80. })}`,
  81. )
  82. }