Skip to main content

Supported Data Types

📘 Logs

Configuration

Advanced tuning fields

These settings are hidden under advanced settings in the UI. For most tables, start with the defaults.

Prerequisites

  • An existing S3 Tables table. Sawmills does not create the table for you in v1.
  • A Sawmills Collector instance with AWS access configured.
  • S3 Tables REST control-plane access for the target table bucket.
  • Table-scoped S3 Tables write access for the target table or table bucket, plus data-plane access to the managed warehouse bucket used by that table.
  • If you will query the same table from Snowflake, create the Snowflake catalog integration and AWS IAM role before you depend on AUTO_REFRESH.

Notes

  • The current S3 Tables destination is logs-only.
  • Partitioning is fixed to service + ts_hour for this exporter path.
  • Existing tables must already match the required schema and partition contract.
  • The advanced knobs trade freshness for larger files, fewer commits, and slower Iceberg metadata growth.
  • Snowflake reads from the same Iceberg table through the AWS Glue catalog path after the table is provisioned correctly.

Tuning guidance

  • If metadata grows too fast or S3 Tables starts creating many tiny files, raise target_partition_file_bytes first.
  • If exporter memory is the main problem, keep partition_buffer_compression=zstd and raise partition_buffer_chunk_target_bytes before shrinking file targets.
  • If sparse services take too long to arrive, lower max_partition_file_age before shrinking file-size targets.
  • If memory usage grows under high-cardinality service fanout, lower max_open_partitions or max_buffered_partition_bytes.
  • If your rows are unusually wide, set max_records_per_partition_file to a positive value instead of aggressively shrinking byte targets.

Efficiency Metrics

The collector now emits additional metrics for this destination:
  • s3tablesexporter.open_partitions
  • s3tablesexporter.buffered_partition_bytes
  • s3tablesexporter.compressed_buffered_partition_bytes
  • s3tablesexporter.partition_buffer_compression_ratio
  • s3tablesexporter.partition_buffer_compression_duration_milliseconds
  • s3tablesexporter.partition_buffer_decompression_duration_milliseconds
  • s3tablesexporter.flush_size
  • s3tablesexporter.file_size
  • s3tablesexporter.files_per_commit
  • s3tablesexporter.flush_latency_milliseconds
  • s3tablesexporter.upload_latency_milliseconds
  • s3tablesexporter.commit_latency_milliseconds
  • s3tablesexporter.commit_conflicts
  • s3tablesexporter.forced_partition_flushes
  • s3tablesexporter.unknown_commit_outcomes

Create The Table

For the current native writer, create an Iceberg table that matches the fixed Sawmills v1 contract exactly. Recommended naming:
  • Namespace: telemetry_logs
  • Table name: logs_service_hour_v1

Required schema

Use these 16 columns:

Required partition spec

Partition the table by:
  • service using the identity transform
  • ts using the hour transform, with partition field name ts_hour

Example AWS CLI create flow

The AWS CLI supports creating S3 Tables Iceberg tables directly. Save the metadata below as s3tables-schema.json and create the table with:
This command requires at least:
  • s3tables:CreateTable
  • s3tables:PutTableData when you pass --metadata
Example metadata file:

AWS-side permissions

The collector needs:
  • S3 Tables REST access to the table bucket
  • table-scoped write permissions such as s3tables:PutTableData on the target table or table bucket
  • any additional S3 Tables actions your provisioning path needs, such as s3tables:GetTable during validation or planning
  • direct S3 access to the managed warehouse bucket behind the table
If you plan to query the same table from Snowflake through the Glue catalog path, you also need to grant the Snowflake side access to:
  • the AWS Glue catalog entry for the table
  • Lake Formation DESCRIBE and SELECT on the target table if Lake Formation is enforcing permissions

Example collector IAM

The collector needs both S3 Tables control-plane access and direct S3 data-plane access to the managed warehouse bucket for the table. Attach the AWS-managed policy:
Then add an inline policy for the managed warehouse bucket:
You can discover the managed warehouse bucket after table creation:
The warehouseLocation value looks like:
Use that bucket name in the inline IAM policy above.

Query From Snowflake

S3 Tables uses the AWS Glue Iceberg REST catalog path. The validation path that worked end to end used:
  • AWS IAM role for Snowflake SigV4 auth
  • Snowflake catalog integration with CATALOG_SOURCE = ICEBERG_REST
  • Snowflake Iceberg table object pointing at CATALOG_TABLE_NAME and CATALOG_NAMESPACE
  • explicit GRANT USAGE ON INTEGRATION
  • explicit ALTER ICEBERG TABLE ... SET AUTO_REFRESH = TRUE

1. Create the AWS IAM role Snowflake will assume

Create a role in your AWS account with permissions for Glue and, if Lake Formation is enabled, the matching Lake Formation table permissions. Permissions policy example:
You can create the role first with a placeholder trust relationship, then tighten it after Snowflake returns the final API_AWS_IAM_USER_ARN and API_AWS_EXTERNAL_ID. Initial trust policy example:

2. Create the Snowflake catalog integration

Use ICEBERG_REST, not the older GLUE catalog integration shape.
Important:
  • In REST_CONFIG, specify WAREHOUSE and do not also specify CATALOG_NAME.
  • The WAREHOUSE format is <aws-account-id>:s3tablescatalog/<table-bucket-name>.
Immediately inspect the integration:
Record these properties:
  • API_AWS_IAM_USER_ARN
  • API_AWS_EXTERNAL_ID
  • REST_CONFIG
  • REST_AUTHENTICATION
Then update the AWS IAM role trust policy so the principal and external ID exactly match the values returned by DESCRIBE INTEGRATION.

3. Grant Lake Formation permissions

If Lake Formation is enabled, grant DESCRIBE and SELECT on the table to the Snowflake AWS role you used in SIGV4_IAM_ROLE.
If your Snowflake environment uses a Snowflake-managed AWS IAM user for the integration handshake, grant the same permissions to that user too:

4. Create the Snowflake Iceberg table object

Create the database, schema, and query role first if they do not already exist:
Then create the Iceberg table object:
If another role will own or query this table, grant it integration usage explicitly:
Then switch to the role that will operate on the table and enable refresh:
Why document it this way:
  • the table object can exist while AUTO_REFRESH is still off
  • the reader can stop on an integration-permission issue
  • the exact fixes were GRANT USAGE ON INTEGRATION ... and ALTER ICEBERG TABLE ... SET AUTO_REFRESH = TRUE

5. Validate Snowflake refresh health

Run this before debugging the collector:
Read these fields carefully:
  • executionState
  • currentSnapshotId
  • lastSnapshotTime
  • pendingSnapshotCount
  • invalidExecutionStateReason
Healthy example:
  • executionState = RUNNING
Common failure patterns:
  • GENERALIZED_PIPE_STOPPED
  • invalidExecutionStateReason mentioning integration access control
  • pendingSnapshotCount growing faster than lastSnapshotTime advances

6. Query it

Example:
If this query fails while the collector is writing successfully, re-check:
  • the table schema matches the 16-column contract above
  • the partition spec is service + hour(ts)
  • AUTO_REFRESH is enabled on the Snowflake table object
  • GRANT USAGE ON INTEGRATION ... was applied to the role you are using
  • Lake Formation grants are present
  • the Snowflake catalog integration trust relationship is valid
  • SYSTEM$AUTO_REFRESH_STATUS(...) is healthy

7. Production operating notes

For a production table, the main operational risk is usually Snowflake refresh lag rather than collector write correctness. Watch these regularly:
  • SYSTEM$AUTO_REFRESH_STATUS(...)
  • executionState
  • lastSnapshotTime
  • pendingSnapshotCount
If executionState is healthy but pendingSnapshotCount keeps growing, Snowflake is falling behind the table’s snapshot churn. In that case:
  • reduce commit frequency from writers if possible
  • avoid unnecessary tiny commits
  • verify the Snowflake reader role still has USAGE on the integration
  • verify Lake Formation access has not drifted
If executionState is not healthy, inspect invalidExecutionStateReason first. That field is usually the fastest path to the real issue.

AWS Credential Configuration

Sawmills Collector runs on Kubernetes. Prefer IAM-based access, such as IRSA on EKS, so the collector can reach both the S3 Tables API and the managed warehouse bucket. If you use a pod role or node role, make sure that role includes:
  • AmazonS3TablesFullAccess
  • the inline managed-warehouse-bucket policy shown above
Only use static credentials if your environment cannot provide a workload identity.