Profile the workload before you pick a service
Most bad analytics choices begin with a service name: "let's use Redshift" or "just put Athena on it". That's like a doctor writing the prescription before asking about symptoms. The standard analytics profile is the history you take first. It's one short document with ten answers. It describes the workload and names no service. Once it's filled in, many services drop out on their own and the choice gets much simpler.
You'll reuse this profile in every service lesson that follows. Each service gets judged against the same ten answers.
The ten questions
| # | Field | Record this | Vague (useless) | Concrete (useful) |
|---|---|---|---|---|
| 1 | Sources and volume | Systems, daily volume, growth, history kept | "lots of data" | "40 GB/day from Postgres + 3M clickstream events/hr, 3 years kept" |
| 2 | Batch or streaming | How data arrives and gets processed | "real-time-ish" | "orders in nightly batch; clicks as a continuous stream" |
| 3 | Freshness | Maximum age of data when someone looks at it | "fast" | "finance: by 07:00 daily; fraud flag: under 10 seconds" |
| 4 | Query patterns and concurrency | Query shapes and the peak number of queries running at once | "many users" | "dashboards, 60 concurrent at 09:00 Monday; ad-hoc SQL, 5 at once" |
| 5 | Users | Analysts, engineers, scientists, and the tools they use | "the business" | "25 analysts (SQL + BI), 4 engineers (Python/Spark), 3 scientists (notebooks)" |
| 6 | Open formats | Must tables be Iceberg, Delta or Hudi, and who reads or writes them? | "open would be nice" | "Iceberg required: Databricks team must read the same tables" |
| 7 | Governance and lineage | Access rules, PII, audit, lineage needs | "secure" | "column-level masking on email; lineage to source for audit" |
| 8 | Residency | Where data may legally live and be processed | "Europe probably" | "UK only (contract clause 14.2)" |
| 9 | Team skills | What the team can run on day one | "we'll learn" | "strong SQL, no Spark or Kafka operators on call" |
| 10 | Cost model | Pay per query, per hour, reserved; who signs off | "cheap" | "predictable monthly bill preferred; budget cap set" |
Gotcha: for concurrency, record the peak, not the headcount. 400 licensed users might mean 40 queries at once at 09:00 on Monday and almost nothing at 15:00. Sizing for 400 wastes money, and sizing for the average leaves the Monday-morning peak queuing.
Walkthrough: profiling a sample workload
Northwind Outdoor is a retailer with 120 stores. Keep the profile as a YAML file in the repo, next to the infrastructure code, so changes go through review.
# analytics-profile.yaml: describes the need, names no service
workload: northwind-sales-and-fraud
sources:
- {name: orders_postgres, volume_per_day: 40GB, arrival: batch_nightly}
- {name: pos_events, volume_per_hour: 3M events (~1.5KB each), arrival: stream}
retention: 3 years raw, 7 years aggregates
freshness:
sales_dashboards: "by 07:00 daily" # batch is fine
fraud_alerts: "< 10 seconds" # needs streaming
concurrency: {dashboards_peak: 60, adhoc_sql_peak: 5}
users: {analysts: 25, engineers: 4, scientists: 3}
open_format: {required: iceberg, reason: "partner team reads tables with Spark"}
governance: {pii_columns: [email, phone], column_masking: true, lineage: required_for_audit}
residency: {region: eu-west-2, basis: "customer contract: UK only"}
skills: {sql: strong, spark: basic, kafka: none}
cost_model: {preference: predictable_monthly, cap_gbp: 15000}
# Tag every line HARD or PREF in the next step
Hard constraints vs preferences
Use one test on each line: if a service misses this, do we walk away, or just work harder or pay more? Walking away makes it a hard constraint. Working harder makes it a preference.
- Hard: residency set by law or contract, a latency SLA such as fraud under 10 seconds, a required open format, and mandatory PII masking.
- Preference: "we know SQL better than Spark" (people can learn), and "predictable bill" (can be traded against features).
Hard constraints filter: they remove options. Preferences rank what's left. Expect pressure to mark everything HARD. If everything is hard, you have no room to trade, so push back and ask "what happens if we miss it?"
Check hard constraints against facts, not memory. To see whether a service runs in your required Region, query the public parameters AWS publishes:
# List the services AWS reports in eu-west-2 (London), then filter for the ones you care about
aws ssm get-parameters-by-path \
--path /aws/service/global-infrastructure/regions/eu-west-2/services \
--query "Parameters[].Value" --output text | tr '\t' '\n' | grep -E '^(athena|redshift|kinesis|firehose)