Data Architecture: Review bidirectional FKs and denormalization between auth/processed transaction tables
Resolved 💬 3 comments Opened Jan 9, 2026 by mhoev Closed Feb 13, 2026
Summary
The current data architecture for transaction reconciliation uses bidirectional foreign keys and field copying between omnipay_auth_txs and processed_txs tables. This pattern warrants review for potential simplification.
Current Design
omnipay_auth_txs processed_txs
───────────────── ─────────────
omnipay_auth_tx_id (PK) processed_tx_id (PK)
auth_code auth_code
auth_date tx_date
auth_at posting_date
card_no card_no
tx_amount tx_amount
... ...
← COPIED FROM processed_txs: ← COPIED FROM omnipay_auth_txs:
merchant_id auth_date
terminal_id auth_time
processed_tx_id (FK →) auth_at
is_matched_processed file_date
card_acceptor_name
omnipay_auth_tx_id (FK →)
is_matched_auth
Issues with Current Approach
- Bidirectional FKs - Both tables reference each other, creating circular dependency
- Data duplication - Fields copied between tables instead of JOINed when needed
- Double writes on reconciliation - Must update BOTH tables, increasing failure risk
- Inconsistency risk - If one update fails, tables are out of sync
- Maintenance overhead - Changes require updating both tables
Additional Complexity
processed_txs is a polymorphic table receiving data from multiple sources:
- Omnipay (matched with
omnipay_auth_txs) - Paysphere (different auth flow)
- Future payment systems
This means the FK relationship and reconciliation logic may need to be source-aware.
Potential Alternatives
Option A: Single FK Direction
-- Only processed_txs references auth
processed_txs.omnipay_auth_tx_id → omnipay_auth_txs.omnipay_auth_tx_id
-- JOIN when auth data needed
SELECT p.*, a.auth_date, a.auth_at
FROM processed_txs p
LEFT JOIN omnipay_auth_txs a USING (omnipay_auth_tx_id)
Option B: Intermediate Reconciliation Table
-- Separate table for the relationship
tx_reconciliation (
processed_tx_id FK,
auth_tx_id FK,
auth_source ENUM('omnipay', 'paysphere', ...),
matched_at TIMESTAMP
)
Option C: Source-specific Auth Tables
-- Each source has its own auth table
omnipay_auth_txs → processed_txs (for Omnipay)
paysphere_auth_txs → processed_txs (for Paysphere)
Questions to Resolve
- Why was the denormalized design chosen? (Performance? Reporting? Legacy compatibility?)
- Do reports/exports depend on the copied fields being in
processed_txs? - Is .NET compatibility a constraint?
- How should multi-source auth be handled long-term?
Impact Assessment Needed
Before refactoring:
- [ ] Audit all queries using copied fields
- [ ] Check report dependencies
- [ ] Evaluate Spanner JOIN performance for this use case
- [ ] Consider migration path for existing data
Related
- Reconciliation performance fix (7-day filter + loop)
auth_timecolumn discrepancy between tables
---
Created during investigation of reconciliation timeout issues
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗