How the two OTP tables behind this dashboard are built. server = Cost tabclient = User-Behavior tab
sms_session_id but no trace_id; the client table has otp_trace_id but no sms_session_id. That's why dashboard filters are split per tab (Country/Channel drive server, Platform/Version drive client).One row = one carrier SMS send, enriched with its verification outcome. Powers the Bad OTP / Cost tab and the supplier analysis. Hive EXTERNAL Parquet, partitioned by dt (yyyyMMdd), at obs_abucc.db/β¦; queried via Grafana StarRocks (fevnxapb1i0hsf).
price present β carrier success; NULL β failure.carrier_to_verify_ms = verify_ts β carrier_ts.-- source names are pseudo; real ODS/log tables live in the pipeline INSERT OVERWRITE TABLE obs_abucc.dwd_botim_logs_server_otp_session_di PARTITION (dt) SELECT s.sms_session_id, s.carrier_ts, s.ptype, s.price, CASE WHEN s.price IS NOT NULL THEN 1 ELSE 0 END AS is_carrier_success, s.to_number, s.purpose, -- 1=SIGNUP 2=VERIFY 8=PAYBY_NOTIFY v.verify_ts, v.verify_reason, v.device_type, v.country_code, v.verify_device_key, CASE WHEN v.verify_reason = 'success' THEN 1 ELSE 0 END AS is_verify_success, v.verify_ts - s.carrier_ts AS carrier_to_verify_ms, s.dt FROM ods_carrier_send_log s LEFT JOIN ods_verify_authcode_log v ON s.sms_session_id = v.sms_session_id AND v.dt = s.dt WHERE s.dt = '${bizdate}';
| column | meaning |
|---|---|
sms_session_id | SMS session UUID β join key to VerifyAuthCode |
ptype | vendor: ks=Etisalat, ys=BytePlus, rs=Transland, cm=CM.com |
price | USD; present β carrier success, NULL β failure |
purpose | 1=SIGNUP Β· 2=VERIFY Β· 8=PAYBY_NOTIFY (payment notify, NOT OTP) |
verify_reason | success / wrongCode / authCodeExpired / fingerprintRisk β¦ |
device_type | 0=Android, 1=iOS (present on success only) |
is_verify_success | 1 = verified, 0 = failed/never |
carrier_to_verify_ms | send β verify duration (ms) |
purpose=8 is payment-notification SMS, not OTP β for OTP analysis filter purpose IN (1,2). Rates use denominator = total sends. Valid history starts 2026-02-09 (earlier days had verify unlogged = false 100% fail).One row = one client UI-track event (OTP Sent / Entered / Verification SuccessΒ·Failed / Block / Page State). Powers the User Behavior tab β the "why users drop off" side the server can't see. Impala Parquet, partition dt.
Source vew.logs_footprint_extra is a firehose: a single json string + dt. Inside, name/when/baseInfo are real nested objects, but payload is a stringified JSON with space-containing keys:
| field group | technique |
|---|---|
payload.* (event, Otp Trace Id, Input Method, Time Stamp) | regexp_extract(pl,'"Key":"([^"]*)"',1) on the payload string |
name / when / baseInfo.* | get_json_object(json,'$.baseInfo.x') |
Impala specifics: STORED AS PARQUET (can't write ORC); from_unixtime(CAST(CAST(ts/1000 AS BIGINT) β¦)) because from_unixtime needs BIGINT. Full script in the repo (analysis/otp_flatten.hql).
device_type = 0=iOS, 1=Android here β OPPOSITE the server table (0=Android,1=iOS). Verified by brand=apple.ts_ms semantics differ by platform: iOS = absolute epoch, Android = relative input-duration, β€4.10 = null. Only ~11% is a real timestamp.otp_trace_id (99% have only that event) β auto-fill conversion/failure unmeasurable until it reuses the session trace_id.failure_reason / otp_block_type mixed EN/Arabic + case variants β normalize.PARTITION (dt=β¦) for multi-day.| server (Cost tab) | client (User-Behavior tab) | |
|---|---|---|
| grain | 1 carrier SMS send | 1 UI-track event |
| built from | carrier-send β VerifyAuthCode on sms_session_id | parsed logs_footprint_extra payload JSON |
| join key | sms_session_id (no trace_id) | otp_trace_id (no sms_session_id) |
| history | 122 days (from 2026-02-09) | 1 day (2026-06-28) |
| answers | delivery Β· vendor Β· cost Β· fail type | funnel Β· blocks Β· auto-fill Β· latency Β· platform |
device_type | 0=Android, 1=iOS | 0=iOS, 1=Android |