Guide · Kinesis Firehose S3 Delivery

Kinesis Firehose S3 Delivery Configuration for MCP Servers — Buffer Hints, Parquet Format Conversion, Compression

Kinesis Firehose's S3 delivery has five independently tunable dimensions — buffer size, buffer interval, S3 prefix, output format, and compression — and the right choices depend on whether you're optimizing for query cost, delivery latency, or storage cost. The defaults (5 MB / 300 seconds, plain JSON, UNCOMPRESSED) are fine for getting started but cost approximately 10× more in Athena query fees than a properly configured Parquet + Snappy pipeline on the same data. Three critical decisions: choose buffer hints based on your latency SLA (60 seconds minimum interval — use it when you need near-real-time Athena visibility; 300–900 seconds for cost-optimized batch archiving); enable format conversion to Parquet for any table you query more than once per day (Athena scans 5–10× less data on columnar Parquet vs row-oriented JSON); and use Hive-compatible S3 prefix patterns (year=YYYY/month=MM/day=DD/hour=HH) so Athena can use partition projection instead of requiring MSCK REPAIR TABLE after every delivery.

TL;DR

Set BufferingHints to 64 MB / 60 seconds for near-real-time Athena queries, or 128 MB / 900 seconds for cost-efficient batch archiving. Use S3 prefix probe-results/year=!{timestamp:yyyy}/month=!{timestamp:MM}/day=!{timestamp:dd}/hour=!{timestamp:HH}/ for Hive partitioning. Enable DataFormatConversionConfiguration with Parquet output and a Glue Data Catalog schema for 5–10× Athena scan reduction. Use Snappy compression with Parquet (native block compression), GZIP for plain JSON (best ratio for text). Always set ErrorOutputPrefix separately — without it, error records land in the success prefix and corrupt your Athena table.

Buffer hint tuning: size vs interval tradeoffs

Firehose buffers records in memory and flushes to S3 when either the size limit or the interval limit is reached — whichever comes first. Lower limits mean more frequent, smaller S3 files; higher limits mean fewer, larger files.

ParameterRangeEffect of lower valueEffect of higher value
SizeInMBs1–128 MBMore frequent delivery; smaller S3 files; higher PUT costLess frequent delivery; larger S3 files; lower PUT cost
IntervalInSeconds60–900 secondsLower delivery latency; more files; higher PUT costHigher delivery latency (up to 15 min); fewer files; lower PUT cost

Buffer presets for AliveMCP use cases:

Use caseSizeInMBsIntervalInSecondsRationale
Near-real-time Athena visibility6460Data visible in Athena within ~2 minutes; 1,440 PUT operations/day/partition
Operational dashboard (5-min freshness)64300Data visible within ~7 minutes; 288 PUTs/day/partition
Cost-optimized batch archive128900Data visible within ~17 minutes; 96 PUTs/day/partition; 15× fewer files than 60s mode
Compliance archive (query-rare)128900Minimize PUT operations; Athena queries infrequent anyway

S3 PUT pricing is $0.005 per 1,000 requests. The cost difference between 60-second and 900-second buffer intervals is minimal in absolute dollars (approximately $0.001/day per partition difference for a single delivery stream). The more important consideration is S3 small-file fragmentation: thousands of 5–10 KB files per prefix cause Athena to spend most of its time on metadata operations rather than actual data scanning, increasing query time and cost even without increasing data volume.

S3 prefix patterns and Hive-compatible partitioning

The S3 prefix determines how Firehose organizes delivered objects. Athena and Glue perform best with Hive-compatible prefixes that use key=value format — these enable partition projection, eliminating manual partition maintenance.

// Hive-compatible prefix (recommended for Athena)
Prefix: 'probe-results/year=!{timestamp:yyyy}/month=!{timestamp:MM}/day=!{timestamp:dd}/hour=!{timestamp:HH}/',
ErrorOutputPrefix: 'probe-results-errors/!{firehose:error-output-type}/year=!{timestamp:yyyy}/month=!{timestamp:MM}/day=!{timestamp:dd}/hour=!{timestamp:HH}/',

// Standard datetime prefix (simpler; requires MSCK REPAIR TABLE for new partitions)
Prefix: 'probe-results/!{timestamp:yyyy/MM/dd/HH}/',
ErrorOutputPrefix: 'probe-results-errors/!{firehose:error-output-type}/!{timestamp:yyyy/MM/dd/HH}/',

With Hive-compatible prefixes, configure Athena partition projection on the Glue table to auto-detect new time partitions without any operator action:

-- Athena table with partition projection (no MSCK REPAIR TABLE needed)
CREATE EXTERNAL TABLE probe_results (
  server_slug   STRING,
  tenant_id     STRING,
  status        STRING,
  response_time_ms BIGINT,
  error_code    STRING,
  probed_at     STRING
)
PARTITIONED BY (year STRING, month STRING, day STRING, hour STRING)
STORED AS PARQUET
LOCATION 's3://alivemcp-probe-archive/probe-results/'
TBLPROPERTIES (
  'projection.enabled'       = 'true',
  'projection.year.type'     = 'integer',
  'projection.year.range'    = '2026,2030',
  'projection.year.digits'   = '4',
  'projection.month.type'    = 'integer',
  'projection.month.range'   = '01,12',
  'projection.month.digits'  = '2',
  'projection.day.type'      = 'integer',
  'projection.day.range'     = '01,31',
  'projection.day.digits'    = '2',
  'projection.hour.type'     = 'integer',
  'projection.hour.range'    = '00,23',
  'projection.hour.digits'   = '2',
  'storage.location.template'= 's3://alivemcp-probe-archive/probe-results/year=${year}/month=${month}/day=${day}/hour=${hour}/'
);

Format conversion: JSON to Parquet via Glue Data Catalog

Format conversion transforms incoming JSON records to Parquet (columnar) or ORC format before writing to S3. Athena scans 5–10× less data on a Parquet table with the same records as a JSON table, because columnar format allows Athena to read only the columns referenced in the query rather than parsing entire JSON records.

Requirements for format conversion:

// Enable format conversion in delivery stream configuration
ExtendedS3DestinationConfiguration: {
  // ... prefix, buffer hints, etc. ...

  DataFormatConversionConfiguration: {
    Enabled: true,
    InputFormatConfiguration: {
      Deserializer: {
        OpenXJsonSerDe: {},  // Parse input as JSON (handles nested objects, arrays)
        // Alternative: HiveJsonSerDe for strict Hive JSON format
      },
    },
    OutputFormatConfiguration: {
      Serializer: {
        ParquetSerDe: {
          Compression: 'SNAPPY',        // SNAPPY (fast, Parquet-native) or GZIP (better ratio)
          BlockSizeBytes: 268435456,     // 256 MB Parquet row group size (default)
          PageSizeBytes: 65536,          // 64 KB page size (default)
          EnableDictionaryCompression: true,  // Dictionary encoding for low-cardinality columns
          WriterVersion: 'V2',           // Parquet writer version (V2 improves null handling)
        },
        // Alternative: OrcSerDe for ORC format
      },
    },
    SchemaConfiguration: {
      RoleARN: 'arn:aws:iam::123456789012:role/firehose-glue-role',
      DatabaseName: 'alivemcp',
      TableName: 'probe_results',
      Region: 'us-east-1',
      VersionId: 'LATEST',  // Always use latest Glue table schema version
    },
  },
}

Format conversion adds approximately 3–5 seconds of processing overhead per buffer flush. This is negligible for batch archiving workloads but means the effective minimum delivery latency is ~65 seconds even with a 60-second buffer interval.

Parquet vs ORC: which to choose

FactorParquetORC
Athena supportFull (preferred format)Full
Compression (Snappy)~3:1 ratio~3:1 ratio
Compression (Zlib/GZIP)~5:1 ratio~5:1 ratio
Read performanceExcellent for column-selective queriesExcellent; slightly faster for full-scan
Complex types (maps, structs)Full supportFull support
Ecosystem compatibilityWider (Spark, Flink, Pandas, Arrow)Strong in Hive/Hadoop ecosystems
Recommendation for MCP dataDefault choiceIf migrating from existing Hive/Hadoop pipeline

Compression options for JSON delivery (without format conversion)

When not using format conversion (delivering plain JSON or NDJSON), Firehose applies compression to the entire S3 object after buffering:

CompressionTypical ratio on JSONCPU costAthena compatibilityWhen to use
UNCOMPRESSED1:1NoneFullDevelopment / debugging only
GZIP~8:1 on repetitive JSONLow (CPU-cheap to decompress)FullDefault for plain JSON — best balance of ratio and compatibility
SNAPPY~3:1Very lowFull (Athena supports SNAPPY)When decompression speed matters more than ratio
HADOOP_SNAPPY~3:1Very lowFullWhen downstream consumers use Hadoop snappy codec
ZIP~8:1LowPartial (Athena cannot read ZIP)Avoid for Athena-queried tables

For AliveMCP probe results as plain JSON, GZIP is the recommended compression. A probe result record averages ~500 bytes uncompressed. With GZIP at 8:1 ratio, a 64 MB buffer holding 128,000 records compresses to ~8 MB — a significant reduction in S3 storage costs over months of data accumulation.

Important: do not use ZIP (ZIP) if the table will be queried by Athena — Athena cannot read ZIP-compressed S3 files directly and will fail with an unreadable format error. Use GZIP instead.

IAM permissions for Firehose S3 delivery

The Firehose service role needs the following S3 permissions on the destination bucket:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "FirehoseS3Delivery",
      "Effect": "Allow",
      "Action": [
        "s3:AbortMultipartUpload",
        "s3:GetBucketLocation",
        "s3:GetObject",
        "s3:ListBucket",
        "s3:ListBucketMultipartUploads",
        "s3:PutObject"
      ],
      "Resource": [
        "arn:aws:s3:::alivemcp-probe-archive",
        "arn:aws:s3:::alivemcp-probe-archive/*"
      ]
    },
    {
      "Sid": "GlueSchemaAccess",
      "Effect": "Allow",
      "Action": [
        "glue:GetTable",
        "glue:GetTableVersion",
        "glue:GetTableVersions"
      ],
      "Resource": [
        "arn:aws:glue:us-east-1:123456789012:catalog",
        "arn:aws:glue:us-east-1:123456789012:database/alivemcp",
        "arn:aws:glue:us-east-1:123456789012:table/alivemcp/probe_results"
      ]
    }
  ]
}

The GetObject and ListBucket permissions are required for Firehose to verify the S3 bucket is accessible during stream creation, not for the actual delivery. Omitting them causes the delivery stream creation to fail with a permission validation error even though the object PUT operation would succeed.

Common failure modes

SymptomCauseFix
Athena query returns no results despite S3 data presentPartition projection range doesn't include current date, or S3 prefix doesn't match projection templateVerify projection range includes today's year; check prefix pattern matches storage.location.template exactly
Parquet conversion fails — all records to error prefixSource JSON doesn't match Glue table schema (extra fields, type mismatch)Use LATEST VersionId and update Glue table schema to match current record format; add nullable columns for new fields
S3 storage costs unexpectedly high despite GZIPParquet format chosen with GZIP but block sizes too small (many small row groups)Increase BufferingHints SizeInMBs to at least 64 MB so each S3 file contains a full Parquet row group
Athena queries slow despite Parquet formatToo many small Parquet files (sub-1 MB), causing metadata overhead to dominateIncrease buffer size to 128 MB to produce fewer, larger Parquet files per hour
Error records appearing in success prefixErrorOutputPrefix not configured — defaults to writing errors to same prefix as successful deliveryAlways set ErrorOutputPrefix to a separate path with !{firehose:error-output-type}
Delivery stream creation fails: access denied on S3Missing GetObject or ListBucket permissions on the delivery roleAdd these permissions to the IAM role even though they aren't used at delivery time