stat-sync.ts 3.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788
  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. const DATALAKE_INGESTION_LAG_MS = 5 * 60_000
  10. export type SyncStatsResult = { ok: true; rows: number; startedAt: string; periodStart: string; periodEnd: string }
  11. export type SyncStatsError = AthenaQueryError | AthenaQueryTimeoutError | DatabaseError
  12. export const syncStats: () => Effect.Effect<
  13. SyncStatsResult,
  14. SyncStatsError,
  15. Athena | ModelStatRepo | ProviderStatRepo | GeoStatRepo
  16. > = Effect.fn("StatSync.sync")(function* () {
  17. const startedAt = yield* DateTime.nowAsDate
  18. const periodEnd = new Date(Math.floor((startedAt.getTime() - DATALAKE_INGESTION_LAG_MS) / 60_000) * 60_000)
  19. const periodStart = new Date(
  20. Date.UTC(periodEnd.getUTCFullYear(), periodEnd.getUTCMonth(), periodEnd.getUTCDate() - 6),
  21. )
  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] = 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. ],
  39. { concurrency: "unbounded" },
  40. )
  41. const modelRows = modelRowsFromAggregates(modelAggregates)
  42. const providerRows = providerRowsFromAggregates(providerAggregates)
  43. const geoRows = geoRowsFromAggregates(geoAggregates)
  44. yield* Effect.all([modelStats.upsert(modelRows), providerStats.upsert(providerRows), geoStats.upsert(geoRows)], {
  45. concurrency: "unbounded",
  46. discard: true,
  47. })
  48. yield* Effect.logInfo(
  49. `stats sync complete ${JSON.stringify({
  50. startedAt: startedAt.toISOString(),
  51. periodStart: periodStart.toISOString(),
  52. periodEnd: periodEnd.toISOString(),
  53. rows: modelRows.length,
  54. providerRows: providerRows.length,
  55. geoRows: geoRows.length,
  56. stage: Resource.App.stage,
  57. })}`,
  58. )
  59. return {
  60. ok: true,
  61. rows: modelRows.length,
  62. startedAt: startedAt.toISOString(),
  63. periodStart: periodStart.toISOString(),
  64. periodEnd: periodEnd.toISOString(),
  65. }
  66. })
  67. function logRuntimeCheck() {
  68. return Effect.logInfo(
  69. `athena stats runtime check ${JSON.stringify({
  70. catalog: Resource.InferenceEvent.catalog,
  71. database: Resource.InferenceEvent.database,
  72. dataset: Resource.StatsSyncConfig.dataset,
  73. table: Resource.InferenceEvent.table,
  74. workgroup: Resource.InferenceEvent.workgroup,
  75. region: Resource.InferenceEvent.region,
  76. stage: Resource.App.stage,
  77. })}`,
  78. )
  79. }