Upgrade to Pro — share decks privately, control downloads, hide ads and more …

[Yahoo!広告] Spring Boot+Neo4jを 使⽤した広告アカウント管理での 階...

[Yahoo!広告] Spring Boot+Neo4jを 使⽤した広告アカウント管理での 階層構造化の実現

Yahoo!広告では、広告運用を効率的に行うために、複数の広告アカウントを階層構造で管理できる機能を提供しています。
広告アカウントのデータストアにはRDBを使用していましたが、機能拡張で階層数を増やそうとしたところ、要求された性能を満たすことができませんでした。
解決手段として、階層構造を表現するのにより適したGraphDBのNeo4jを導入しました。
本セッションでは、Spring BootアプリケーションでRDBを利用しながら、Neo4jを導入した事例を発生した課題と対策を含めて紹介します。

発表者
武知 茉美 / LINEヤフー株式会社, エンジニア
2019年度にヤフー株式会社(現: LINEヤフー株式会社)に新卒入社。広告のアカウントシステムの開発。

※この資料は以下イベントで発表した内容です
https://jjug.doorkeeper.jp/events/164154

More Decks by LINEヤフーTech (LY Corporation Tech)

Other Decks in Technology

Transcript

  1. © LY Corporation ⾃⼰紹介 2 武知 茉美 経歴 • 2019

    ヤフー (現 LINEヤフー) 新卒⼊社 ü 広告アカウントPFの開発を担当 趣味 • ⾳楽ライブに⾏くこと • ラジオ
  2. © LY Corporation ⾃⼰紹介 3 ⾼⾒ 拓也 • 2012 ヤフー

    (現 LINEヤフー) ⼊社 ü 広告アカウントPFの開発を担当 ü Javaサポートチームにも兼務し、ヤフーのJava開発をサポート • 2021~2023/9 第11代、第12代 Java⿊帯として活動 ※⿊帯制度 「ある分野に突出した知識とスキルを持っているその分野の第⼀⼈者」とし て実績を上げている優秀な⼈財を称賛し、新たな活躍の場を提供することを ⽬的として過去Yahoo! Japanにて運⽤されていた制度です。
  3. © LY Corporation 広告の構成 7 アカウント テキスト広告 動画広告 画像広告 ターゲティング

    地域、曜⽇・時間、年齢など 掲載期間 広告予算 広告主 広告代理店 インターネット ユーザー
  4. © LY Corporation 広告の構成 8 アカウント テキスト広告 動画広告 画像広告 ターゲティング

    地域、曜⽇・時間、年齢など 掲載期間 広告予算 広告主 広告代理店 インターネット ユーザー
  5. © LY Corporation 9 アカウント構造 アカウントは広告アカウント、MCCアカウントに分類 広告アカウント 検索広告向け 広告の掲載期間、予算を管理する ディスプレイ広告向け

    MCCアカウント “マイ クライアント センター” の略 MCC、広告アカウントを束ねるもの 広告商材単位、運⽤組織単位に束ねることで、 • 広告運⽤の効率化、 • アクセス権限管理の簡略化 を実現できる
  6. © LY Corporation 10 アカウント構造 MCCを広告商材で分割するケース ⾃動⾞メーカー MCC 商⽤向け MCC

    レジャー向け 広告アカウント 広告アカウント 広告アカウント ターゲティング ターゲティング 広告アカウント
  7. © LY Corporation 宝⽯ブランド 広告アカウント 11 アカウント構造 MCCを運⽤組織で分割するケース 広告代理店 MCC

    A課 MCC B課 電機メーカー 広告アカウント スポーツメーカー 広告アカウント ⼦供服ブランド 広告アカウント A課 従業員 B課 従業員
  8. © LY Corporation Oracleの課題 クエリを最適化した場合のアカウント構造を表さない複雑なデータ構造 代理店 MCC1 MCC2 広告アカウント 11

    広告アカウント 12 広告アカウント 21 広告アカウント 22 広告アカウント 31 検索のために代理店からのリレーションを追加した
  9. © LY Corporation GraphDB GraphDBは階層構造やグラフ構造をそのままデータで表し、効率的な検索を実施するデータベース https://neo4j.com/developer/guide-data-modeling/ ノード 頂点の部分。ラベル を付けて種別や分類 を⽰す。

    リレーションシップ ノードとノードの関 係を表現する。⽅向 とラベルをもつ。 プロパティ ノードやリレーショ ンシップにおける属 性情報。
  10. © LY Corporation Neo4jのCypherクエリ match path=(p:ACCOUNT)-[*]->(c:ACCOUNT) where p.accountName = ‘代理店1’

    and (c.accountName = ‘広告アカウント21’ or c.accountName = ‘広告アカウント41’) return c; (例)代理店1の広告アカウントをアカウント名で探すクエリ 代理店1 MCC1 MCC2 広告アカ ウント12 広告アカ ウント21 広告アカ ウント22 広告アカ ウント31 MCC4-1 広告アカ ウント41 広告アカ ウント42 MCC4-2 MCC4-3 広告アカ ウント11
  11. © LY Corporation 導⼊後のシステム構成 26 ⼊稿PFなどの他PFはアカウント構造以外の⽬的でOracleを参照しているので、マスタ情報は Oracleで保持し、Neo4jには得意なアカウント構造の検索のみを任せる構成とした。 Oracle: マスタデータの管理 Neo4j:

    アカウント構造の検索 代理店 MCC1 MCC2 広告アカ ウント11 広告アカ ウント12 広告アカ ウント21 広告アカ ウント22 広告アカ ウント31 ・アカウントID ・アカウント名 ・アカウントID ・アカウント名 ・詳細情報1 ・詳細情報2 ・その他・・・
  12. © LY Corporation • アカウント構造のグラフ検索 • グラフ検索に必要なデータのみ保持 • マスタ情報の保持 •

    Neo4jで保持するデータのマスタも保持 • Oracleだけで⼗分な性能がでる検索は、今 まで通りOracleのみ利⽤ Neo4j Oracle 28 Neo4JとOracleの使い分け・両⽴
  13. © LY Corporation Neo4jとOracleの使い分け・両⽴ 29 代理店 MCC1 MCC2 MCC11 MCC12

    MCC21 広告アカ ウント 211 広告アカ ウント 111 広告アカ ウント 112 広告アカ ウント 121 検索例 Neo4jに対して検索 ・ID=121 ・名=広告アカウント121 ・アカウントID ・アカウント名 ・詳細情報1 ・詳細情報2 ・その他・・・ Oracle ヒット ・ID=211 ・名=広告アカウント211 検索結果 マージ Neo4jとOracleのデータをマージする
  14. © LY Corporation ソフトウェア構成 31 Neo4j driver Neo4j Spring Data

    Neo4j Oracle Oracle JDBC driver MyBatis Spring Yahoo!広告アプリケーション Spring Boot Spring Data JDBC Neo4j関連 • Oracle は、MyBatis Spring を利⽤。 • Neo4j は、Spring Data Neo4j を利⽤。
  15. © LY Corporation Spring Data Neo4j 実装例 32 // Spring

    Data Neo4j の実装例 // interfaceクラスを作り、@Queryアノテーションを使ってクエリーを書くのみ @Repository public interface AccountRepository extends Neo4jRepository<Account, Long> { @Query("MATCH (p:Account)-[:RELATION*]->(c:Account) " + "WHERE c.name = $name RETURN c") List<Account> findByName(@Param("name") String name); }
  16. © LY Corporation Neo4jとOracle両⽅でのアトミック性の担保 課題 34 Oracleで管理しているデータとNeo4jのデータの⼀貫性を保ちたい。 Neo4j更新1 Oracle更新1 Oracle更新2

    Oracleトランザクション Neo4jトランザクション Oracle更新2で処理失敗した場合、Oracleの更新処理はロールバックされるが、Neo4jの更新はロールバッ クされず成功となる。 コミット ロールバック
  17. © LY Corporation Neo4jとOracle両⽅でのアトミック性の担保 解消⽅法 35 Oracleで管理しているデータとNeo4jのデータの⼀貫性を保つために、コミット反映を ほぼ同時にする⾃作TransactionManagerを導⼊。 Neo4j更新1 Oracle更新1

    Oracle更新2 Oracleトランザクション Neo4jトランザクション Oracle更新2で処理失敗した場合、OracleとNeo4j両⽅の更新処理がロールバックされ、 成功した場合はどちらもコミットされる。 ⾃作TransactionManager ロールバック or コミット ロールバック or コミット
  18. © LY Corporation OracleNeo4jTransactionManager 36 public class OracleNeo4jTransactionManager implements PlatformTransactionManager

    { private final final List<PlatformTransactionManager> transactionManagers; // OracleとNeo4jのTransactionManagerを保持する public OracleNeo4jTransactionManager(DataSourceTransactionManager oracle, Neo4jTransactionManager neo4j) { this.transactionManagers = Arrays.asList(oracle,neo4j); } @Override public void commit(TransactionStatus status) throws TransactionException { // 別途、詳細説明 } @Override public void rollback(TransactionStatus status) throws TransactionException { // 別途、詳細説明 } ・・・ }
  19. © LY Corporation commit 37 /** * Oracle、Neo4jのトランザクションをCommitする *どちらかがエラーとなった場合は、ExceptionをThrowしてロールバック */

    @Override public void commit(TransactionStatus status) throws TransactionException { Exception commitException = null; for (PlatformTransactionManager transactionManager : this.transactionManagers) { try { transactionManager.commit(status); } catch (Exception ex) { commitException = ex; } } if (commitException != null) { throw new HeuristicCompletionException(HeuristicCompletionException.STATE_COMMITTED, commitException); } }
  20. © LY Corporation rollback 38 /** * Oracle、Neo4jのトランザクションをRollbackする *どちらかがエラーとなった場合は、マニュアルリカバリ */

    @Override public void rollback(TransactionStatus status) throws TransactionException { Exception rollbackException = null; for (PlatformTransactionManager transactionManager : this.transactionManagers) { try { transactionManager.rollback(status); } catch (Exception ex) { rollbackException = ex; } } if (rollbackException != null) { throw new UnexpectedRollbackException("Rollback exception", rollbackException); } }
  21. © LY Corporation 使い⽅ 39 /** * @Transactionalの合成 */ @Target({ElementType.TYPE,

    ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Transactional(transactionManager="oracleNeo4jTransactionManager",rollbackFor=Throwable.class) public @interface OracleNeo4jTransactionManager { } @OracleNeo4jTransactional public void add ( AccountOperation operation ) { // OracleのInsert、Neo4jのcreate node }
  22. © LY Corporation OracleのTemporary Tableの使⽤ 40 Neo4j ① アカウント構造検索 ②

    検索結果をOracleの Temporary Tableへ書き出し ③ Temporary Tableとマスタデータを 結合して検索 Temporary Tableのデータはトランザクション終了時に破棄される。 Neo4jとOracleのデータをマージするためにOracleのTemporary Tableを利⽤することとした。 Temporary Table アカウントID アカウント名 Oracle マスタデータ アカウントID アカウント名 詳細情報1 詳細情報2
  23. © LY Corporation OracleのTemporary Tableの性能課題 42 Neo4jの検索結果をOracleで使⽤するためにTemporary Tableを使⽤したが、Temporary Table への書き込みで性能課題が発⽣した。

    Neo4j Oracle ① アカウント構造検索 ② 検索結果をOracleの Temporary Tableへ書き出し ③ Temporary Tableとマスタデータを 使⽤した検索 JDBCのBatch Insertに変更することで50ミリ秒程度の性能で安定した INSERT ALL を使⽤したTemporary Tableへの書き込みでは性能が安定せず、ランダムに性能 劣化した。早い時は50ミリ秒だが遅い時は60秒かかる。
  24. © LY Corporation Neo4jのORMapperのエラー 45 リリース後1⽇数⼗件、SpringDataNeo4jでクラスにフィールドが存在するのにフィールドが存在しないとい う再現性のないMappingExceptionが発⽣した。 throwable:org.springframework.data.mapping.MappingException: Cannot map

    constructor parameter accountId to a property of class class jp.co.yahoo.anemos.abs.shared.biz.auth.v2.dto.ClientAccountDto throwable:org.springframework.data.mapping.MappingException: Cannot map constructor parameter accountName to a property of class class jp.co.yahoo.anemos.abs.shared.biz.auth.v2.dto.ClientAccountDto エラー例① エラー例②
  25. © LY Corporation ノードEntity Class-based Projection 46 ノードEntityとClass-based Projection @Node(“ACCOUNT”)

    @Data @ToString public class AccountEntity { @Id @Property(“accountId”) private Long accountId; @Property(“createdOn”) private OffsetDateTime createdOn; @Property(“accountName”) private String accountName; ・ ・ ・ } /** * AccountEntityのClass-based Projectionクラス */ @Getter public class ClientAccountDto { private final Long accountId; private final String accountName; public ClientAccountDto(Long accountId, String accountName) { this.accountId = accountId; this.accountName = accountName; } } SpringDataNeo4jで DTOの詰め替え
  26. © LY Corporation AccountEntityのRepository 47 @Repository public interface AccountNeo4jRepository extends

    Neo4jRepository<AccountEntity, Long> { /** * 親アカウント配下に紐づく⼦アカウント⼀覧を取得する */ @Query(・・・) List<ClientAccountDto> findClientAccountList( @Param("managerAccountId") Long managerAccountId ); ・・・ }
  27. © LY Corporation MappingExceptionの対策 DTOへの変換を⾃作した 48 @Repository public interface AccountNeo4jRepository

    extends Neo4jRepository<AccountEntity, Long> { /** * 親アカウント配下に紐づく⼦アカウント⼀覧を取得する */ @Query(・・・) List<AccountEntity> findClientAccountList( @Param("managerAccountId") Long managerAccountId ); ・・・ } public class AccountServiceImpl implements AccountService { private final AccountNeo4jDao accountNeo4jDao; @Override public List<ClientAccountDto> findClientAccountList(Long managerAccountId) { // Neo4jから取得したAccountEntityをDTOに変換する return accountNeo4jDao.findClientAccountList(managerAccountId).stream() .map(r -> new ClientAccountDto(・・・)).toList(); } }
  28. © LY Corporation リリース後の課題と対処案 49 代理店 MCC1 広告アカウント BBB 広告アカウント

    CCC リ ク エ ス ト リ ク エ ス ト MCC1-1 広告アカウント AAA 10万件・・・ 広告アカウント DDD Neo4jが得意 Oracleの⽅が得意で、Neo4jの性能劣化を確認している