Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Go Night Talks – After Conference 登壇資料 Hikari
Search
UPSIDER, Inc. Tech&Product div.
October 14, 2025
580
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Go Night Talks – After Conference 登壇資料 Hikari
UPSIDER, Inc. Tech&Product div.
October 14, 2025
More Decks by UPSIDER, Inc. Tech&Product div.
See All by UPSIDER, Inc. Tech&Product div.
時価総額から逆算する、日々開発価値_ PL/BSで読み解くエンジニアリング貢献_Kinsho
upsider_tech
1
140
最近、評価が難しくなった ── 変わったのは評価項目ではなく、見方と見せ方_Mitsui
upsider_tech
0
11
TypeSpecで繋ぐ複数プロダクトの型安全 — スキーマ共有による「型契約」の実践_mitsui
upsider_tech
0
20
400超のデータポイントを型で制す — UPSIDERの与信審査エンジンを支えるTypeScriptの「柔軟性」と「結合力」_泉雄介
upsider_tech
0
440
フルスタックTypeScriptで挑む、UPSIDER法人審査システムの再構築_Mitomi
upsider_tech
0
370
TypeScriptの型はAIに届いている か?_shotaro
upsider_tech
1
1.2k
AI時代におけるプロダクト開発のあり方 を考える_Akari
upsider_tech
0
200
品質を保ちながら、 プロダクトの核に迫るための取り組み_Tanaka Yoshihiro
upsider_tech
0
450
CxOを動かす会社の条件_Akanuma
upsider_tech
0
310
Featured
See All Featured
エンジニアに許された特別な時間の終わり
watany
108
250k
State of Search Keynote: SEO is Dead Long Live SEO
ryanjones
0
220
New Earth Scene 8
popppiees
3
2.4k
Measuring & Analyzing Core Web Vitals
bluesmoon
9
880
Templates, Plugins, & Blocks: Oh My! Creating the theme that thinks of everything
marktimemedia
31
2.8k
The Cost Of JavaScript in 2023
addyosmani
55
10k
Navigating Weather and Climate Data
rabernat
0
390
DBのスキルで生き残る技術 - AI時代におけるテーブル設計の勘所
soudai
PRO
67
56k
Money Talks: Using Revenue to Get Sh*t Done
nikkihalliwell
0
400
Max Prin - Stacking Signals: How International SEO Comes Together (And Falls Apart)
techseoconnect
PRO
0
200
職位にかかわらず全員がリーダーシップを発揮するチーム作り / Building a team where everyone can demonstrate leadership regardless of position
madoxten
64
56k
Odyssey Design
rkendrick25
PRO
2
730
Transcript
株式会社UPSIDER Hikari Kosuda 決済加盟店の名寄せを加速する 高速名称判別の仕組み Goのマスコットキャラクター The Go gopher(Gopherくん)のオリジナルは、 Renée
Frenchによるデザインです。
⾃⼰紹介 © UPSIDER, Inc. 2 Hikari Kosuda • 2021/10からUPSIDERにJOIN ◦
その前に副業でもちょこっと参加 • GoはUPSIDERで初めて使い始めました ◦ それまではPython/Javaなど • 決済基盤開発チームで、カード発⾏や決済処理などを開発 • ワーケーションを割とやってます ◦ 最後に⾏ったのは5⽉の宮城県(松島と気仙沼) @kosuda-h @ksdev_xxx
今回話すこと © UPSIDER, Inc. 3 • クレカ決済の加盟店名を扱う難しさ • 前⽅⼀致に絞った⾼速アルゴリズム •
実装のサンプルとポイント • Goで書いて良かったところ
カード決済の流れ カード決済 システム 加盟店 オーソリ(承認要求) クリアリング(売上確定) ◯◯円で決済します OK/NG
◯◯円請求します カード利用者 4
クレカ決済の加盟店名の難しさ © UPSIDER, Inc. 5 なんのこっちゃわからん名前の決済が来ることも • XXX⾜⽴インター店 → XXX ADACHIINTATEN •
Facebook広告 → FACEBK *XYZ1234abc (←ランダム⽂字列) • FooJapan → HuuZIYAP ※実際の加盟店名ではない。なお、実際はもっとわかりにくいものも存在する。
それでも加盟店は同定したい! © UPSIDER, Inc. 6 このような機能には必須! • 利⽤先制限 ◦ Amazon専⽤カード、広告専⽤カード、etc…
• 不正利⽤対策 ◦ 過去に不正利⽤でしか使われていない加盟店名パターンでブロック • ポイント付与 ◦ 〇〇広告で利⽤したらポイント1.5倍!キャンペーンなど
加盟店の同定⽅法 © UPSIDER, Inc. 7 決済情報にある、名前とIDを利⽤する • 名前(今回の主題) ◦ 加盟店が⾃由に決めるもの
◦ 毎回同じとは限らない → ある程度、パターンでマッチさせたい ▪ オーソリ(承認要求)とクリアリング(売上確定)で変わったり ▪ サービスに関するIDがついていたり e.g. FACEBK *XYZ1234abc ▪ チェーン店など、店名がついていたり e.g. XXX ADACHIINTATEN • ID ◦ 加盟店を管理する、アクワイアラという管理会社によって定められる ◦ 毎回同じとは限らない ▪ 名前と同じ問題が、⼤体ある
加盟店名パターンの⼀致チェック⽅法 © UPSIDER, Inc. 8 正規表現だと間に合わないので、前⽅⼀致に絞る! Goの正規表現の速度が〜〜〜という話ではない 多くの加盟店に対し、それぞれ複数の名前パターンがありうる → 毎回の決済ごとに、1000個とかの⽐較を回せないのが問題 もし、1個が0.001秒でも全体で1秒かかる
決済処理全体の⽬標時間が1秒なので、これは許容できない 多くの加盟店では、前⽅⼀致で判定可能なことに着⽬ 前⽅⼀致なら、アルゴリズムを⼯夫することで、O(N)ではなくO(n)にできる ※N = 名前パターン数、n = 名前パターンの最⼤⽂字数 とした場合
効率的な前⽅⼀致の⼿法 © UPSIDER, Inc. 9 Trie(トライ)⽊というデータ構造を⽤いる トライ木(Trie木) の解説と実装【接頭辞(prefix) を利用したデータ構造】 https://algo-logic.info/trie-tree/
名前パターンを挿入する時は、 rootから該当文字のnodeを辿っていく 存在しない場合は文字nodeを追加する 最後のnodeにマーキングする 名前からマッチするパターンを検索する時は、 1⽂字⽬から順にこの⽊を辿るだけでどのパターン に該当するかわかる! root node 例えば「firemap」をこの木で検索すると、 「firema」まで辿ることができる。 最後に通った◎の位置から、「fire」のパターンに一致 するとわかる node: 各頂点。今回だと文字に対応する。 root: 木の大元のnode。ここから文字を辿る。
Trie⽊の実装(定義‧初期化‧Insert) © UPSIDER, Inc. 10 // 初期化はシンプルに、rootを用意するだけ func InitTrie() (*Trie,
error) { return &Trie{ root: &TrieNode{children: map[rune]*TrieNode{}}, expectedChars: ExpectedCharsEBCDIC, }, nil } // 名前パターンのInsert func (t *Trie) Insert(pattern string, patternID string) { current := t.root for _, r := range pattern { // 次の文字のノードが既にあれば、移動する if v, ok := current.children[r]; ok { current = v continue } // まだなければ、ノードを作って移動する current.children[r] = &TrieNode{children: map[rune]*TrieNode{}} current = current.children[r] } // Insertする名前パターンの全文字分ノード移動したら、最終地点にIDをセット current.patternID = patternID } // ノード(節点、頂点) type TrieNode struct { children map[rune]*TrieNode patternID string // 名前パターンIDを持てる } // 木の本体 type Trie struct { root *TrieNode expectedChars string } // 使える文字列は定義しておくとGood // 今回は、加盟店名にありうる文字列のみにする var ExpectedCharsEBCDIC = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTU VWXYZ0123456789 !?.,:;<>()¥+-*/=&%@#|'\"`_"
Trie⽊の実装(検索) © UPSIDER, Inc. 11 func (t *Trie) GetMatchedIDs(key string)
([]string, error) { matchedPatternIDs := make([]string, 0, len(key)) current := t.root // rootからスタート! for _, r := range key { // 次の文字に対応するノードがあれば進む v, found := current.children[r] if !found { // 対応するノードがなければ終わり break } current = v // 道中、名前パターンのIDを見つけたら拾っておく if current.patternID != "" { matchedPatternIDs = append(matchedPatternIDs, current.patternID) } } // マッチした名前パターン群を返す。実際の運用では、最後にマッチしたIDを参照する return matchedPatternIDs, nil }
Goでよかったこと © UPSIDER, Inc. 12 • 実装 ◦ シンプルで書きやすい ◦
標準のベンチマークテストで⼗分な確認ができた(Go 1.17当時でも) • 運⽤ ◦ 加盟店名検索をマイクロサービス化したかったため、相性◯ • その他 ◦ 今後、カナなどの名称を扱いたい場合もruneによるUnicode対応が容易
まとめ © UPSIDER, Inc. 13 決済における加盟店の同定、Goでどう解決する? • 正規表現だと、どうしても間に合わない...! • Trie⽊での前⽅⼀致なら、クレカ決済の制約にも耐えられるパフォーマンスに!
• Goだとシンプルに書けて、マイクロサービスとの相性も◯でいいね! スペシャルサンクス スライド内のGopherくんを描いてくれた、 弊社のMiki Masumotoさん( :@m_miki0108)
Appendix:ベンチマークテスト © UPSIDER, Inc. 14 O(n)とは⾔ったが、実際どのくらいのパフォーマンスが出るのか? func Benchmark_Search(b *testing.B) {
trieTree, _ := partialmatch.InitTrie() insertTimes := 10000 // Trie木に入れるランダムワードの個数 // ①10~20文字のワードをランダム生成して、木に Insertする patterns, ids := createRandomWordsAndIDs(trieTree, b, insertTimes) patternHolder := createMerchantInfos(patterns, ids) _ = trieTree.Insert(patternHolder) // ② ①のいずれかのワードを選び、一致 &不一致のワードをランダム生成 // ↑をb.N回繰り返す (ベンチマーク package側で調整してくれる ) wordsToFind := createRandomWordsToFind(trieTree, b, b.N, patterns) b.ResetTimer() // ③計測開始! for i := 0; i < b.N; i++ { word := wordsToFind[i] _ = trieTree.GetMatchedIDs(word) // ④検索! } } go test -bench=Benchmark_Search -benchmem … Benchmark_Search-10 513416 2276 ns/op 4686 B/op 10 allocs/op … 10~20文字 * 1万語のTrie木に対し、大量のランダム検索を 行った。 結果、1回あたり2276 nsで検索できると計測された! ※スペースの関係で、ランダム生成関数の実装詳細は省略した