Skip to main content

Rich Menu Stat Calculation

Overview

The report that answers "which button on the menu gets tapped the most" is produced by aggregating tracking_log records against each menu's button layout, stored in the rich_menu_action table.

The job is split into two levels so it scales:

  • calculate_rich_menu_stat — the fan-out job, which determines which rich menus need recalculating and breaks the work into per-menu jobs
  • calculate_rich_menu_stat_item — the job that does the actual calculation for a single rich menu

This split keeps a menu with a huge log volume from blocking the others, and allows retries at the individual menu level.

Business Flow

Cron trigger (profile cron-scheduler)

  1. On the hour (0 * * * *), RichMenuStatScannerService scans Redis for keys prefixed with RICH_MENU_STAT_DIRTY: followed by a rich menu id. These are set by the tracking path whenever a user taps a menu button.
  2. It extracts the id and publishes one job per rich menu onto calculate_rich_menu_stat_item.
  3. If no dirty keys exist, the cycle is skipped entirely.

calculate_rich_menu_stat (profile main)

processCalculateRichMenuStat finds every rich menu still marked active and publishes a calculate_rich_menu_stat_item job for each one — a full-sweep fan-out.

calculate_rich_menu_stat_item (profile main)

  1. Load the rich menu joined with all its rich_menu_action rows, capturing type, label, link (the original destination before the tracking redirect wraps it), and index.
  2. transformRichMenu reshapes this into a single menu object with its list of actions.
  3. Pull that menu's tracking_log rows and summarise them with summarizeRichMenuStats:
    • totalClick — total taps per button
    • uniqueClick — count of distinct line_uid values
  4. The result is a statData array of entries carrying type, label, link, totalClick, and uniqueClick.
  5. Write the result back to rich_menu and to rich_menu_archive for member/guest menus that keep archived snapshots.

Key Files & Functions

  • internal/richmenu/consumer.goOnProcessCalculateRichMenuStat, OnProcessCalculateRichMenuStatItem, the RichMenuStatPayload struct
  • internal/richmenu/service_stats.go
    • Service.calculateRichMenuStats(ctx, richMenuInfo)
    • transformRichMenu(), summarizeRichMenuStats()
    • the statData and statItem structs (fields Type, Label, Link, TotalClick, UniqueClick)
  • internal/richmenu/service_methods.goprocessCalculateRichMenuStat(), processCalculateRichMenuStatItem()
  • internal/cronscheduler/rich_menu_stat_scanner.goRichMenuStatScannerService.Run(ctx) and the constant richMenuDirtyKeyPrefix = "RICH_MENU_STAT_DIRTY:"
  • Queues: calculate_rich_menu_stat and calculate_rich_menu_stat_item (profile main), published by the cron running under the cron-scheduler profile

Connections to Other Services

  • Tables: tracking_log (raw data), rich_menu plus rich_menu_action (button layout), and rich_menu_archive (statistic snapshots for menus that have been replaced)
  • Redis: keys prefixed RICH_MENU_STAT_DIRTY: mark which menus have new data awaiting calculation
  • Consumers of the output: cms-api-go and cms-web on the rich menu report screens
  • This job makes no LINE API calls