Freika/dawarich · error · Stats::HexagonCalculator::PostGISError

Failed to calculate H3 hexagon centers: #{e.message}

Error message

Failed to calculate H3 hexagon centers: #{e.message}

What it means

Stats::HexagonCalculator::PostGISError raised from the rescue in calculate_hexagons: every StandardError thrown while batching through the user's monthly points (each_coordinate_batch) and computing H3 indexes (build_h3_hash) is wrapped and re-raised as PostGISError with the original message appended. Despite the class name, PostGIS itself is only one possible cause - H3 conversion failures, Date argument errors from start_timestamp/end_timestamp (DateTime.new with an invalid year/month), or a dropped PG connection all surface identically here. ExceptionReporter is called first when defined.

Source

Thrown at app/services/stats/hexagon_calculator.rb:62

    if h3_hash.empty?
      Rails.logger.info "No H3 hex IDs calculated for user #{user.id}, #{year}-#{month} (no data)"
      return nil
    end

    if h3_hash.size > MAX_HEXAGONS
      Rails.logger.warn "Too many hexagons (#{h3_hash.size}), using lower resolution"
      # Try with lower resolution (larger hexagons)
      lower_resolution = [h3_resolution - 2, 0].max
      Rails.logger.info "Recalculating with lower H3 resolution: #{lower_resolution}"
      return calculate_hexagons(lower_resolution)
    end

    Rails.logger.info "Generated #{h3_hash.size} H3 hexagons at resolution #{h3_resolution} for user #{user.id}"
    h3_hash
  rescue StandardError => e
    message = "Failed to calculate H3 hexagon centers: #{e.message}"
    ExceptionReporter.call(e, message) if defined?(ExceptionReporter)
    raise PostGISError, message
  end

  def start_timestamp
    (DateTime.new(year, month, 1) - 2.days).to_i
  end

  def end_timestamp
    (DateTime.new(year, month, -1, 23, 59, 59) + 2.days).to_i
  end

  def points
    return @points if defined?(@points)

    tz = user.timezone_iana
    @points = user
              .points
              .not_anomaly
              .without_raw_data

View on GitHub (pinned to 97fad417c5)

Solutions

  1. Read the message suffix - it is the wrapped original error ('Failed to calculate H3 hexagon centers: <original>') and points at the real cause
  2. For H3/coordinate errors, run Points::AnomalyFilter over the affected range, then retry the job
  3. For timeouts, raise the statement timeout or split the range; the calculator already batches at 50k rows and auto-lowers H3 resolution above 10k hexagons
  4. Validate year/month are real calendar values (1..12, plausible year) before enqueuing Stats::CalculatingJob

Example fix

# before: job body lets PostGISError kill the job with no trace of the cause
Stats::HexagonCalculator.new(user_id, year, month).call

# after: rescue, log full cause, and re-enqueue once
begin
  Stats::HexagonCalculator.new(user_id, year, month).call
rescue Stats::HexagonCalculator::PostGISError => e
  Rails.logger.error("hexagons failed for #{year}-#{month}: #{e.message} cause=#{e.cause&.message}")
  retry_job wait: 5.minutes if executions < 2
end
Defensive patterns

Strategy: try-catch

Validate before calling

# Reject impossible periods before enqueuing stats jobs
raise ArgumentError, 'bad period' unless year.between?(1970, 2100) && month.between?(1, 12)

Type guard

def calculable_period?(year, month)
  year.to_i.between?(1970, 2100) && month.to_i.between?(1, 12)
end

Try / catch

begin
  Stats::HexagonCalculator.new(user_id, year, month).call
rescue Stats::HexagonCalculator::PostGISError => e
  # e.message ends with the wrapped original - log e.cause for the real stack
  Rails.logger.error("hexagons failed #{year}-#{month}: #{e.message} cause=#{e.cause&.class}")
  retry_job wait: 10.minutes if executions < 3  # transient DB faults benefit from retry
end

Prevention

When it happens

Trigger: Stats::CalculatingJob running for a user/month whose points include coordinates H3 rejects (NaN, infinity, out-of-range lat/lng) via H3.from_geo_coordinates; an invalid year/month (0, 13, negative) making DateTime.new raise; a query timeout or lost connection mid-batch on a very large month.

Common situations: Bad GPS data imported before anomaly filtering runs, stats jobs enqueued with garbage year/month values, statement timeouts on months with hundreds of thousands of points, or transient DB failovers during background stat recomputation.


AI-assisted analysis of Freika/dawarich@97fad417c5 (2026-08-21). Data as JSON: /api/errors/a3655957f55b6039. Report an issue: GitHub.