Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
Go Night Talks – After Conference 登壇資料 Hikari
Search
UPSIDER, Inc. Tech&Product div.
October 14, 2025
610
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.
なぜレスポンスボディを読み切るのか - Go 1.27で進化した net/http のコネクション再利用メカニズム_Ryu
upsider_tech
1
150
エンジニアはどこまで越境するべきか_Take
upsider_tech
9
3.4k
『⽌めない』を設計する 制約の中で、事業の根幹を⽀える判断_terry
upsider_tech
0
34
English for Shy Engineers
upsider_tech
0
130
時価総額から逆算する、日々開発価値_ PL/BSで読み解くエンジニアリング貢献_Kinsho
upsider_tech
1
200
最近、評価が難しくなった ── 変わったのは評価項目ではなく、見方と見せ方_Mitsui
upsider_tech
0
37
TypeSpecで繋ぐ複数プロダクトの型安全 — スキーマ共有による「型契約」の実践_mitsui
upsider_tech
0
33
400超のデータポイントを型で制す — UPSIDERの与信審査エンジンを支えるTypeScriptの「柔軟性」と「結合力」_泉雄介
upsider_tech
0
770
フルスタックTypeScriptで挑む、UPSIDER法人審査システムの再構築_Mitomi
upsider_tech
0
420
Featured
See All Featured
Building AI with AI
inesmontani
PRO
1
1.2k
First, design no harm
axbom
PRO
2
1.3k
What's in a price? How to price your products and services
michaelherold
247
13k
The Invisible Side of Design
smashingmag
301
52k
Designing Experiences People Love
moore
143
24k
The untapped power of vector embeddings
frankvandijk
2
1.9k
State of Search Keynote: SEO is Dead Long Live SEO
ryanjones
0
280
WCS-LA-2024
lcolladotor
0
830
Leveraging Curiosity to Care for An Aging Population
cassininazir
1
500
JavaScript: Past, Present, and Future - NDC Porto 2020
reverentgeek
52
6.1k
Producing Creativity
orderedlist
PRO
348
41k
Building Experiences: Design Systems, User Experience, and Full Site Editing
marktimemedia
1
610
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で検索できると計測された! ※スペースの関係で、ランダム生成関数の実装詳細は省略した