Design and Implementation of a Scalable Fund Routing Decision Engine at Shopee
Shopee’s scalable fund‑routing decision engine combines a configurable Go‑based rule engine with Redis‑backed, Lua‑driven quota control to dynamically match loan orders to external capital providers, enforce multi‑dimensional volume limits, achieve tens of thousands TPS, and provide extensible risk‑management capabilities across markets.
As Shopee expands in overseas markets, its credit‑related services (e‑commerce installment, cash‑loan, etc.) have grown. To scale, comply with local regulations, and control business risk, Shopee needs to connect external capital providers through joint‑loan and asset‑securitization (ABS) mechanisms.
The fund‑routing platform acts as a bridge between external financial institutions and internal credit products. Because each external capital provider imposes requirements on loan users, loan orders and loan amounts, the platform must match each loan order with a suitable capital provider while respecting volume limits and cost constraints.
The core of the solution is a highly extensible rule decision module and a high‑performance, precise transaction volume‑control module .
Module Design
The fund‑routing decision engine is an independent unit within the fund‑integration system. It receives loan‑related data, applies capital‑provider rules, enforces loan‑volume limits, and outputs the selected capital provider. The overall flow is illustrated below.
The engine is invoked by the transaction service, with clearly defined inputs (loan information) and outputs (selected capital provider).
Capital‑provider order configuration : flexible ordering of providers.
Rule configuration : flexible definition of basic rules, source data, and provider‑specific rules.
Routing time windows : providers can be disabled for specific time periods.
Lazy loading of source data : only load data required by a particular provider.
Threshold management : complex threshold structures can be configured per rule.
Custom validation functions : allow bespoke logic for copied rules.
Rule engine : a component embedded in the application that separates business decisions from code using predefined semantic modules.
Real‑time quota calculation and control : precise per‑day, per‑period, per‑loan‑type accounting to avoid overselling.
Metric reporting : decision results are written to a time‑series database for dashboards and alerts.
Rule Decision Module
Capital providers impose constraints such as prohibited occupations, loan‑amount ranges, minimum age, or minimum credit limit. The platform also requires that the provider’s interest rate be lower than the user‑facing rate. Hard‑coding these checks would be inflexible, so a configurable rule engine is needed.
A rule consists of an operator (e.g., >, <, =, range), a data source (user age, loan amount, etc.), and a threshold (age range, allowed provinces). These are stored in database tables for dynamic use.
Two tables are defined for the rule metadata:
rule_define_tab : stores basic rule definitions (operator code, pre‑processing, description).
rule_item_tab : stores attribute metadata (attribute name, source, type, loading mode).
funder_rule_info_tab : links providers to specific rules, attributes, and thresholds for the rule engine.
Rule Engine Research and Application
Most financial systems use Java‑based engines (Drools, Esper, Activiti, Flowable). Since Shopee’s service is built in Go, the team evaluated Go rule engines and selected govaluate for its simplicity and performance. A typical rule execution looks like this:
func Compare(left interface{}, operator string, right interface{}) (bool, error) {
var params = make(map[string]interface{})
params["left"] = left
params["right"] = right
var expr *govaluate.EvaluableExpression
expr, _ = govaluate.NewEvaluableExpression(fmt.Sprintf("left %s right", operator))
eval, err := expr.Evaluate(params)
log.Infof("expr=%v,params=%+v,result=%+v", expr.String(), params, eval)
if err != nil {
return false, err
}
if result, ok := eval.(bool); ok {
return result, nil
}
return false, errors.New("convert error")
}The function receives the attribute value (left), the threshold (right), and the operator, all configured in the database. It evaluates the expression and returns a boolean result, providing a highly extensible and configurable rule check.
Volume‑Control Module
The system must limit loan exposure on multiple dimensions (total balance, daily count, etc.). Direct updates to a relational database become a bottleneck under high concurrency.
MySQL Pessimistic Lock
Using row‑level locks to update a counter yields ~200 TPS with ~5 ms per update, insufficient for large‑scale financial traffic.
Queue‑Based Asynchronous Processing
Transactions are cached, then persisted via a message queue with multi‑copy and disk‑write guarantees. Although this supports high concurrency, it introduces complexity: cache refresh must recompute thresholds, and a deduplication table is needed to avoid double processing.
Redis Cache + Lua Script
Insert operations are cheap; the bottleneck is balance updates. The solution caches quota information per provider in Redis and performs atomic check‑and‑decrement using a single‑threaded Lua script. Successful checks insert a DB record; a background job later aggregates these records back to the DB.
Cache keys follow the pattern funder_hotspot_limit_khash_{funderId} . Cache refresh calculates the usable quota as threshold - (db_used + pending_db) , then atomically updates the Redis hash.
The control flow (dark nodes = application logic, light nodes = Lua script) ensures no overselling while maintaining high throughput. Tests on a machine with Redis 6.2.2 and MySQL 5.6 show the combined cache‑check‑insert pattern can sustain tens of thousands of TPS under heavy load.
Application and Monitoring
After functional and performance testing, the solution met all requirements: flexible rule configuration, precise loan‑quota control, and high‑throughput processing. To guard against rule mis‑configurations, metrics such as rule‑rejection count and match count are collected and visualized via Prometheus, with alerts for abnormal spikes.
Conclusion and Outlook
The deployed engine routes loan orders across multiple markets, offering flexible rule configuration, strong extensibility, and precise multi‑dimensional control without any overselling. The rule‑decision approach can be applied to other risk‑control scenarios (e.g., SMS/notification gating), while the hot‑spot cache solution is useful for high‑concurrency payment or marketing controls. Future work includes packaging rules as reusable rule‑packs for easier business activation.
Shopee Tech Team
How to innovate and solve technical challenges in diverse, complex overseas scenarios? The Shopee Tech Team will explore cutting‑edge technology concepts and applications with you.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.