Quickstart: S3 to Athena
In about ten minutes you will load public sample JSON from S3 into your own S3 bucket as Parquet, registered as a table in the AWS Glue Data Catalog, and query it in Amazon Athena.
Skipprd reads the JSON, infers its schema, creates the Glue database and table for you, and writes the data. You only provide a bucket and permissions.
Before you begin
You need:
- Skipprd installed. Check with
skipprd --version, orpython -c "import skippr"if you use Python. See Install. - An AWS account and credentials for an IAM principal that can:
- read
s3://skippr-public-sample-data/bike-hire/(s3:ListBucket,s3:GetObject); - read and write your output bucket (
s3:ListBucket,s3:GetObject,s3:PutObject,s3:DeleteObject); - create and update Glue databases, tables, and partitions;
- run Athena queries in your workgroup.
- read
- An S3 bucket for the output in the region you will query from. This guide uses one bucket,
your-output-bucket, for both the Parquet data and Athena query results.
Export your credentials, then create an empty working directory. Skipprd reads skippr.yml from the current directory, so run every command from there.
export AWS_ACCESS_KEY_ID="your-access-key-id"
export AWS_SECRET_ACCESS_KEY="your-secret-access-key"
export AWS_DEFAULT_REGION="us-east-1"
mkdir skippr-athena && cd skippr-athenaAny standard AWS credential source works instead of access keys, such as AWS_PROFILE or an instance role. Use the region your output bucket and Athena workgroup live in.
1. Describe the pipeline
A pipeline has a source (where data comes from), a destination (where it lands), and for Athena a schema sink (where table definitions go — here, the Glue Data Catalog). Each tab below writes the same skippr.yml. Replace your-output-bucket with your bucket name.
from skippr import Config, DataSinkAthena, DataSourceS3, LocalStorage, Pipeline, SchemaSinkGlue
cfg = Config().workspace("quickstart").storage(LocalStorage())
sample = cfg.data_source(
"sample",
DataSourceS3(s3_bucket="skippr-public-sample-data", s3_prefix="bike-hire"),
)
glue = cfg.schema_sink(
"glue",
SchemaSinkGlue(
s3_bucket="your-output-bucket",
s3_prefix="data/bikehire",
athena_workgroup_name="primary",
athena_results_s3_bucket="your-output-bucket",
glue_database_name="skippr_quickstart",
),
)
athena = cfg.data_sink(
"athena",
DataSinkAthena(
s3_bucket="your-output-bucket",
s3_prefix="data/bikehire",
athena_workgroup_name="primary",
athena_results_s3_bucket="your-output-bucket",
),
schema_sink=glue,
)
cfg.pipeline("bikehire", Pipeline(data_source=sample, data_sink=athena))
cfg.save("skippr.yml")skipprd --workspace quickstart --storage-mode local connect data-source s3 \
--pipeline bikehire \
--name sample \
--s3-bucket skippr-public-sample-data \
--s3-prefix bike-hire
skipprd connect data-sink athena \
--pipeline bikehire \
--name athena \
--s3-bucket your-output-bucket \
--s3-prefix data/bikehire \
--athena-workgroup-name primary \
--athena-results-s3-bucket your-output-bucket
skipprd connect schema-sink glue \
--pipeline bikehire \
--name glue \
--s3-bucket your-output-bucket \
--s3-prefix data/bikehire \
--athena-workgroup-name primary \
--athena-results-s3-bucket your-output-bucket \
--glue-database-name skippr_quickstartskippr:
workspace: quickstart
skipprd_el_storage_mode: local
pipelines:
bikehire:
data_source: data_sources.sample
data_sink: data_sinks.athena
data_sources:
sample:
S3:
s3_bucket: skippr-public-sample-data
s3_prefix: bike-hire
data_sinks:
athena:
schema_sink: schema_sinks.glue
Athena:
s3_bucket: your-output-bucket
s3_prefix: data/bikehire
athena_workgroup_name: primary
athena_results_s3_bucket: your-output-bucket
schema_sinks:
glue:
Glue:
s3_bucket: your-output-bucket
s3_prefix: data/bikehire
athena_workgroup_name: primary
athena_results_s3_bucket: your-output-bucket
glue_database_name: skippr_quickstartWhat the settings mean:
athena_results_s3_bucketis a bucket name, not ans3://URI.glue_database_nameon the Glue schema sink names the database the table is registered in. The Athena destination takes it from the linked schema sink, so you set it once.- With the CLI, add the destination before the schema sink:
connect schema-sinkattaches to the pipeline's existing destination. LocalStorage()(skipprd_el_storage_mode: local) keeps Skipprd's own state, such as the discovered schema and how far it has read, in./data. That is fine for a trial; for production, see State store.
2. Discover the schema
import skippr
s = skippr.Session(skippr.Config.discover().get_pipeline("bikehire"))
s.discover()skipprd discover --pipeline bikehire
skipprd schema --pipeline bikehirediscover samples the JSON objects under the prefix and records each field's name and type. It does not write to your bucket. skipprd schema prints the fields it found, which become the columns of your Athena table.
The first command that uses a connector downloads it, so the first run takes a little longer.
3. Sync
import skippr
s = skippr.Session(skippr.Config.discover().get_pipeline("bikehire"))
s.sync(once=True)skipprd sync --pipeline bikehire --onceSync reads every object under the prefix and writes it to Skipprd's write-ahead log (WAL) first. A crash retries that committed batch. Athena overwrites the same files, so you do not get a second copy. Skipprd then creates the skippr_quickstart Glue database and the bikehire table if they do not exist, and writes Parquet files under s3://your-output-bucket/data/bikehire/bikehire/.
--once (once=True) runs a single pass and exits. Without it, sync keeps running and checks the source for new objects on a schedule (every 900 seconds by default; set sync_frequency_seconds on the pipeline to change it).
Check it worked
Open the Athena query editor in the same region, select the primary workgroup and the skippr_quickstart database, and run:
SELECT count(*) AS row_count FROM skippr_quickstart.bikehire;You should see a non-zero row_count. Then look at the data:
SELECT * FROM skippr_quickstart.bikehire LIMIT 10;The columns match the fields skipprd schema printed in step 2. The table is named after the pipeline, bikehire.
Run skipprd sync --pipeline bikehire --once again and repeat the count. It does not change: Skipprd remembers which objects it has already read.
Troubleshooting
AccessDeniedreadingskippr-public-sample-data— your credentials are missing or lack S3 read access. Runaws sts get-caller-identityto confirm which principal Skipprd uses, then grants3:ListBucketands3:GetObjecton the sample bucket.AccessDeniedon a GlueCreateDatabase,CreateTable, or partition call — the principal cannot manage the Glue Data Catalog. Grant Glue create, get, and update permissions on theskippr_quickstartdatabase and its tables.AccessDeniedwriting toyour-output-bucket— grants3:PutObject(ands3:ListBucket,s3:GetObject,s3:DeleteObject) on the bucket.- Athena cannot find
skippr_quickstart— the console is in a different region from the one you synced to. Switch to the region inAWS_DEFAULT_REGION. - Athena asks for a query result location — the
primaryworkgroup has no output location. In the workgroup or query editor settings, set it tos3://your-output-bucket/athena-results/. - The count is zero or the table is missing after sync — sync did not finish. Rerun with
--log(or--log debug) to see each step and the error that stopped it. Records that fail to convert go to a deadletter instead of the table. - The first run hangs while fetching a connector — the machine cannot reach
install.skippr.ioover HTTPS. See Install.
For more, see Troubleshooting.
Next steps
- How Skipprd works — the path from source to WAL to destination.
- Athena destination and Glue schema sink — every option.
- S3 source — point the pipeline at your own bucket and prefix.
- Transforms — split records into tables, partition by time, and order rows.
- Exactly-once delivery — what a crash or retry does at each destination.
