Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Block join toranomaki

4,803 views

Published on

第16回 Lucene/Solr勉強会で発表させて頂いたスライドです

Published in: Engineering
  • Be the first to comment

Block join toranomaki

  1. 1. Block-Join 虎の巻 デジタル・インフォメーション・テクノロジー株式会社 海老澤 志信
  2. 2. 自己紹介 • 海老澤 志信 • 所属 • 現業務 リクルートグループの検索システム コンサル・開発・保守・運用・サイト導入 • Solr歴 Luceneのリリースノートに日本語で名前が載った唯一の男 (2015/05/13現在) ※詳しくは前回のスライドにて…… http://www.slideshare.net/ebisawashinobu/heliosearch-vs-solr
  3. 3. 本日のアジェンダ (Solr4.10系を調べていた時) ・このschema.xmlのパラメータなんだろう <field name="_root_" type="string" indexed="true" stored="false"/>
  4. 4. 本日のアジェンダ (Solr4.10系を調べていた時) ・このschema.xmlのパラメータなんだろう ↓ ・Block-Join Queryという機能で使うらしい <field name="_root_" type="string" indexed="true" stored="false"/>
  5. 5. 本日のアジェンダ (Solr4.10系を調べていた時) ・このschema.xmlのパラメータなんだろう ↓ ・Block-Join Queryという機能で使うらしい ↓ ・Block-Join って何……? <field name="_root_" type="string" indexed="true" stored="false"/>
  6. 6. 本日のアジェンダ (Solr4.10系を調べていた時) ・このschema.xmlのパラメータなんだろう ↓ ・Block-Join Queryという機能で使うらしい ↓ ・Block-Join って何……? ↓ (ネットの海へ) <field name="_root_" type="string" indexed="true" stored="false"/>
  7. 7. 本日のアジェンダ (Solr4.10系を調べていた時) ・このschema.xmlのパラメータなんだろう ↓ ・Block-Join Queryという機能で使うらしい ↓ ・Block-Join って何……? ↓ (ネットの海へ) あまりにも情報がなかったので 調べてみてわかったアレコレをお届けします! <field name="_root_" type="string" indexed="true" stored="false"/>
  8. 8. 序章. 見よう!Block-Join
  9. 9. Block-Joinとは • Solr4.5でリリース https://issues.apache.org/jira/browse/SOLR-3076 • Document(※)に親子関係を持たせ、 それらのIndex登録と検索を行うための機能 ※以降、本資料内ではDocと略す
  10. 10. Block-Joinが出来る事(1) • 関連付けたいDocをネストすることで 親子関係のDocとして登録できる id:parent_1 Field1:Foo Field2:Bar id:child_1 Field_C1:hoge Field_C2:any
  11. 11. Block-Joinが出来る事(1) • 関連付けたいDocをネストすることで 親子関係のDocとして登録できる ※多重ネストは出来ない id:parent_1 Field1:Foo Field2:Bar id:child_1 Field_C1:hoge Field_C2:any id:child_1_1 Field:xxx Field2:yyy
  12. 12. Block-Joinが出来る事(2) • 親Docを検索軸に設定し 紐付く子Docを取得する • 子Docを検索軸に設定し 紐付く親Docを取得する 基本的にはこの2パターン。
  13. 13. Block-Joinが出来る事(2) ●在庫(子)にケーキを持つ店舗(親)を検索したい 【親:店舗】 【子:在庫】
  14. 14. Block-Joinが出来る事(2) ●在庫(子)にケーキを持つ店舗(親)を検索したい 【親:店舗】 【子:在庫】 「コンビニ」を 結果として得る
  15. 15. Block-Joinが出来る事(2) ●スーパー(親)を検索し、在庫(子)を取得したい 【親:店舗】 【子:在庫】
  16. 16. Block-Joinが出来る事(2) ●スーパー(親)を検索し、在庫(子)を取得したい 【親:店舗】 【子:在庫】 「カステラ」と 「プリン」を 結果として得る
  17. 17. Block-Joinが出来る事(2) ID 店舗名 品名 価格 1 A店 ○○ 100 2 A店 △△ 200 3 A店 ☓☓ 300 4 B店 ○○ 100 5 B店 △△ 200 <従来のIndex構成> ID 店舗名(親) 品名(子) 価格(子) 1 A店 1-1 ○○ 100 1-2 △△ 200 1-3 ☓☓ 300 2 B店 2-2 ○○ 100 2-3 △△ 200 <Block-Joinを使用したIndex構成> ●Block-Joinの検索軸イメージ
  18. 18. Block-Joinが出来る事(2) ID 店舗名 品名 価格 1 A店 ○○ 100 2 A店 △△ 200 3 A店 ☓☓ 300 4 B店 ○○ 100 5 B店 △△ 200 <従来のIndex構成> ID 店舗名(親) 品名(子) 価格(子) 1 A店 1-1 ○○ 100 1-2 △△ 200 1-3 ☓☓ 300 2 B店 2-2 ○○ 100 2-3 △△ 200 <Block-Joinを使用したIndex構成> ●Block-Joinの検索軸イメージ 店舗でも品名でも 検索できるが 検索軸は並行構成 店舗でも品名でも 検索できるが 検索軸が分かれている
  19. 19. 閑話休題:Block-Joinへの興味 ●だいたいここまで調べた時点での感覚
  20. 20. 閑話休題:Block-Joinへの興味 ●だいたいここまで調べた時点での感覚 複数店舗の商品検索をするサイトとか コースの予約管理をするサイトで この機能をいい感じに使えないか?
  21. 21. 閑話休題:Block-Joinへの興味 ●だいたいここまで調べた時点での感覚 [既存の課題] ・Solrの1Doc内にマスタとサブの情報が混在している →マスタ情報更新時の影響がサブ情報にも発生する ・検索軸が複数存在しているケース →更新頻度の高い情報と低い情報の混在 複数店舗の商品検索をするサイトとか コースの予約管理をするサイトで この機能をいい感じに使えないか?
  22. 22. 二章. できる!Block-Join
  23. 23. Block-Joinの基本ルール 1. schema.xmlに”_root_”があること →親子関係を保証するためのFieldと値が Index内に自動的にセットされる。 (ユーザが意識する必要はない) <field name="_root_" type="string" indexed="true" stored="false"/>
  24. 24. Block-Joinの基本ルール 2. 全ての親Docには、 識別するための一律のFieldを 定義すること id:parent_1 Field1:Foo Doc_Type:”parent” id:child_1 Field_C1:hoge Field_C2:any
  25. 25. Block-Joinの基本ルール 2. 全ての親Docには、 識別するための一律のFieldを 定義すること id:parent_1 Field1:Foo Doc_Type:”parent” id:child_1 Field_C1:hoge Field_C2:any識別用のField名・型・値は 検索軸となる親Docで一律であれば 何でもいい
  26. 26. <add> <doc> <field name="id">1</field> <field name="title">Solr has block join support</field> <field name="content_type">parentDocument</field> <doc> <field name="id">2</field> <field name="comments">SolrCloud supports it too!</field> </doc> </doc> </add> Block-Joinのドキュメント登録 ・XMLで登録する場合の例
  27. 27. <add> <doc> <field name="id">1</field> <field name="title">Solr has block join support</field> <field name="content_type">parentDocument</field> <doc> <field name="id">2</field> <field name="comments">SolrCloud supports it too!</field> </doc> </doc> </add> Block-Joinのドキュメント登録 ・XMLで登録する場合の例 (1)親Doc識別用Field
  28. 28. <add> <doc> <field name="id">1</field> <field name="title">Solr has block join support</field> <field name="content_type">parentDocument</field> <doc> <field name="id">2</field> <field name="comments">SolrCloud supports it too!</field> </doc> </doc> </add> Block-Joinのドキュメント登録 ・XMLで登録する場合の例 (2)親Docの中に子Docをネスト
  29. 29. Block-Joinのドキュメント登録 ・SolrJで登録する場合の例 // Parent Doc SolrInputDocument product01 = new SolrInputDocument(); product01.addField( "id", "product01" ); product01.addField( "name", "car" ); product01.addField( "content_type", "product" ); // Children SolrInputDocument part01 = new SolrInputDocument(); part01.addField( "id", "part01" ); part01.addField( "name", "wheels" ); part01.addField( "content_type", "part" ); // Add children to parent product01.addChildDocument( part01 );
  30. 30. // Parent Doc SolrInputDocument product01 = new SolrInputDocument(); product01.addField( "id", "product01" ); product01.addField( "name", "car" ); product01.addField( "content_type", "product" ); // Children SolrInputDocument part01 = new SolrInputDocument(); part01.addField( "id", "part01" ); part01.addField( "name", "wheels" ); part01.addField( "content_type", "part" ); // Add children to parent product01.addChildDocument( part01 ); Block-Joinのドキュメント登録 ・SolrJで登録する場合の例 (1)親Docは今までどおり SolrInputDocumentを生成して Field設定
  31. 31. // Parent Doc SolrInputDocument product01 = new SolrInputDocument(); product01.addField( "id", "product01" ); product01.addField( "name", "car" ); product01.addField( "content_type", "product" ); // Children SolrInputDocument part01 = new SolrInputDocument(); part01.addField( "id", "part01" ); part01.addField( "name", "wheels" ); part01.addField( "content_type", "part" ); // Add children to parent product01.addChildDocument( part01 ); Block-Joinのドキュメント登録 ・SolrJで登録する場合の例 (2)子Docも独立した SolrInputDocumentとして Field設定
  32. 32. // Parent Doc SolrInputDocument product01 = new SolrInputDocument(); product01.addField( "id", "product01" ); product01.addField( "name", "car" ); product01.addField( "content_type", "product" ); // Children SolrInputDocument part01 = new SolrInputDocument(); part01.addField( "id", "part01" ); part01.addField( "name", "wheels" ); part01.addField( "content_type", "part" ); // Add children to parent product01.addChildDocument( part01 ); Block-Joinのドキュメント登録 ・SolrJで登録する場合の例 (3)親Docのインスタンスが持つ addChildDocumentメソッドを呼び出し 子Docのインスタンスを紐付け
  33. 33. Block-Join 検索Query 1. 親Docを検索軸に設定し 紐付く子Docを取得する 基本構文: q={!child of=<allParents>}<someParents>
  34. 34. Block-Join 検索Query 1. 親Docを検索軸に設定し 紐付く子Docを取得する 基本構文: ・allParentsは、親Docを識別するためのパラメータ →親Docに一律定義したFieldと値をセット ・someParentsはメインQueryとなる検索条件 →{!child of}では親Docが検索軸となる q={!child of=<allParents>}<someParents>
  35. 35. Block-Join 検索Query 2. 子Docを検索軸に設定し 紐付く親Docを取得する 基本構文: q={!parent which=<allParents>}<someChildren>
  36. 36. Block-Join 検索Query 2. 子Docを検索軸に設定し 紐付く親Docを取得する 基本構文: ・allParentsは、親Docを識別するためのパラメータ →子Doc用の識別Fieldではない ・ someChildrenはメインQueryとなる検索条件 →{!parent which}では子Docが検索対象となる q={!parent which=<allParents>}<someChildren>
  37. 37. Block-Join 検索Query ポイント: ・親Doc検索(parent which)でも 子Doc検索(child of)でも パラメータは親Doc用の識別Fieldを使用 ・親Doc識別パラメータは 単一条件しか指定できない。 (ORやAND、RangeQueryは使用不可能) {!parent which=DocType:parent} {!child of=Group:2}
  38. 38. Block-Joinを使うメリット ■ Index更新量の削減 ID 店舗名 品名 価格 1 A店 ○○ 100 2 A店 △△ 200 3 A店 ☓☓ 300 4 B店 ○○ 100 5 B店 △△ 200 <従来のIndex構成>
  39. 39. Block-Joinを使うメリット ■ Index更新量の削減 ID 店舗名 品名 価格 1 A店 ○○ 100 2 A店 △△ 200 3 A店 ☓☓ 300 4 B店 ○○ 100 5 B店 △△ 200 →”A店”の店舗名が”A店X支所”に変わった… <従来のIndex構成>
  40. 40. Block-Joinを使うメリット ■ Index更新量の削減 ID 店舗名 品名 価格 1 A店X支所 ○○ 100 2 A店X支所 △△ 200 3 A店X支所 ☓☓ 300 4 B店 ○○ 100 5 B店 △△ 200 →”A店”の店舗名が”A店X支所”に変わった… →店舗名が変わるだけで、属する 全てのDocの更新が必要だった <従来のIndex構成>
  41. 41. Block-Joinを使うメリット ■ Index更新量の削減 ID 店舗名(親) 品名(子) 価格(子) 1 A店 1-1 ○○ 100 1-2 △△ 200 1-3 ☓☓ 300 2 B店 2-2 ○○ 100 2-3 △△ 200 →”A店”の店舗名が”A店X支所”に変わった <親子関係を使用したIndex構成>
  42. 42. Block-Joinを使うメリット ■ Index更新量の削減 ID 店舗名(親) 品名(子) 価格(子) 1 A店X支所 1-1 ○○ 100 1-2 △△ 200 1-3 ☓☓ 300 2 B店 2-2 ○○ 100 2-3 △△ 200 →”A店”の店舗名が”A店X支所”に変わった →親Docしか持たない情報は 親Docの更新だけで完結する <親子関係を使用したIndex構成>
  43. 43. Block-Joinを使うメリット ■ Index更新量の削減 ID 店舗名(親) 品名(子) 価格(子) 1 A店 1-1 ○○ 100 1-2 △△ 200 1-3 ☓☓ 300 2 B店 2-2 ○○ 100 2-3 △△ 200 →”A店”が廃止になった <親子関係を使用したIndex構成>
  44. 44. Block-Joinを使うメリット ■ Index更新量の削減 ID 店舗名(親) 品名(子) 価格(子) 1 A店 1-1 ○○ 100 1-2 △△ 200 1-3 ☓☓ 300 2 B店 2-2 ○○ 100 2-3 △△ 200 →”A店”が廃止になった →”A店”の親Docを削除すると…… <親子関係を使用したIndex構成>
  45. 45. Block-Joinを使うメリット ■ Index更新量の削減 ID 店舗名(親) 品名(子) 価格(子) 1 A店 1-1 ○○ 100 1-2 △△ 200 1-3 ☓☓ 300 2 B店 2-2 ○○ 100 2-3 △△ 200 →”A店”が廃止になった →”A店”の親Docを削除すると…… →ネストしている子Docも連動して削除される <親子関係を使用したIndex構成>
  46. 46. Block-Joinを使うメリット ■ Index更新量の削減 ID 店舗名(親) 品名(子) 価格(子) 1 A店 1-1 ○○ 100 1-2 △△ 200 1-3 ☓☓ 500 2 B店 2-2 ○○ 100 2-3 △△ 200 →ネストした子Docも独立したDoc扱いなので 子Docだけの更新や削除は可能 <親子関係を使用したIndex構成>
  47. 47. Block-Joinを使うメリット ■ Index更新量の削減 ID 店舗名(親) 品名(子) 価格(子) 1 A店 1-1 ○○ 100 1-2 △△ 200 1-3 ☓☓ 300 1-4 □□ 600 2 B店 2-2 ○○ 100 2-3 △△ 200 →ただし、今ある親子関係のDocに対して 子Docだけを追加することはできない <親子関係を使用したIndex構成>
  48. 48. Block-Joinを使うメリット ■ Index更新量の削減 ID 店舗名(親) 品名(子) 価格(子) 1 A店 1-1 ○○ 100 1-2 △△ 200 1-3 ☓☓ 300 1-4 □□ 600 2 B店 2-2 ○○ 100 2-3 △△ 200 →ただし、今ある親子関係のDocに対して 子Docだけを追加することはできない →追加するDocを含めた親子全てのDoc更新が必要 <親子関係を使用したIndex構成>
  49. 49. 三章. 戦え!Block-Join
  50. 50. Block-Joinの気になるところ (1) Block-Join Queryの検索軸に指定した 親/子Docは検索結果で同時に取得できない
  51. 51. Block-Joinの気になるところ (1) Block-Join Queryの検索軸に指定した 親/子Docは検索結果で同時に取得できない ID 店舗名(親) 品名(子) 価格(子) 1 A店 1-1 ○○ 100 1-2 △△ 200 1-3 ☓☓ 300 2 B店 2-2 ○○ 100 2-3 △△ 200 ・親Docを検索軸にすると 子DocのFieldしか結果で得られない
  52. 52. Block-Joinの気になるところ (1) Block-Join Queryの検索軸に指定した 親/子Docは検索結果で同時に取得できない ID 店舗名(親) 品名(子) 価格(子) 1 A店 1-1 ○○ 100 1-2 △△ 200 1-3 ☓☓ 300 2 B店 2-2 ○○ 100 2-3 △△ 200 ★既存のQueryを使う場合はネストの影響がなく すべて独立したDocとして扱われる
  53. 53. Block-Joinの気になるところ (1) Block-Join Queryの検索軸に指定した 親/子Docは検索結果で同時に取得できない ID 店舗名(親) 品名(子) 価格(子) 1 A店 1-1 ○○ 100 1-2 △△ 200 1-3 ☓☓ 300 2 B店 2-2 ○○ 100 2-3 △△ 200 ★既存のQueryを使う場合は ネストの影響がなく独立したDocとして扱われる親/子Docを同時に検索したい場合は Block-Join Queryは使用しないこと
  54. 54. Block-Joinの気になるところ (2) ネストした親子Doc間において 直接親子関係を取得する手段がない
  55. 55. Block-Joinの気になるところ (2) ネストした親子Doc間において 直接親子関係を取得する手段がない ID 店舗名(親) 品名(子) 価格(子) 1 A店 1-1 ○○ 100 1-2 △△ 200 1-3 ☓☓ 300 2 B店 2-1 ○○ 100 2-2 △△ 200 検索軸を親Docとして「A店 OR B店」とした時
  56. 56. ID 店舗名(親) 品名(子) 価格(子) 1 A店 1-1 ○○ 100 1-2 △△ 200 1-3 ☓☓ 300 2 B店 2-1 ○○ 100 2-2 △△ 200 Block-Joinの気になるところ (2) ネストした親子Doc間において 直接親子関係を取得する手段がない 検索軸を親Docとして「A店 OR B店」とした時 Block-Join Queryでそれぞれの子Docがヒットする 子Docがどちらの店舗に属しているかがわからない
  57. 57. Block-Joinの気になるところ (2) ネストした親子Doc間において 直接親子関係を取得する手段がない ID 店舗名(親) 店舗ID(子) 品名(子) 価格(子) 1 A店 1-1 1 ○○ 100 1-2 1 △△ 200 1-3 1 ☓☓ 300 2 B店 2-1 2 ○○ 100 2-2 2 △△ 200 →必要であれば、親子Doc間で情報を共有するように Index構成を作る この例では、子に親DocのIDを持たせることで 親Docの情報を検索して引っ張ることが可能となる。
  58. 58. Block-Joinの気になるところ (3) Block-Join Queryでは検索軸に指定した 親/子Docの絞り込みができない
  59. 59. Block-Joinの気になるところ (3) Block-Join Queryでは検索軸に指定した 親/子Docの絞り込みができない q={!parent which=DocType:店舗} 品物:○○ →子Docに品物”○○”を持つ店舗(親Doc)を検索する
  60. 60. Block-Joinの気になるところ (3) Block-Join Queryでは検索軸に指定した 親/子Docの絞り込みができない q={!parent which=DocType:店舗} 品物:○○ →子Docに品物”○○”を持つ店舗(親Doc)を検索する →メインQueryでは、子Docを検索対象とするため 親Docを絞り込む条件を記述することが出来ない
  61. 61. Block-Joinの気になるところ (3) Block-Join Queryでは検索軸に指定した 親/子Docの絞り込みができない q={!parent which=DocType:店舗} 品物:○○ &fq=店舗種別:コンビニ →絞り込みたい親Docを対象とした FilterQueryを追加する。
  62. 62. Block-Joinの気になるところ (3) Block-Join Queryでは検索軸に指定した 親/子Docの絞り込みができない ID 店舗名(親) 店舗種別(親) 品名(子) 価格(子) 1 A店 コンビニ 1-1 ○○ 100 1-2 △△ 200 1-3 ☓☓ 300 2 B店 ドラッグストア 2-1 ○○ 100 2-2 △△ 200 q={!parent which=DocType:店舗} 品物:○○ &fq=店舗種別:コンビニ
  63. 63. Block-Joinの気になるところ (3) Block-Join Queryでは検索軸に指定した 親/子Docの絞り込みができない ID 店舗名(親) 店舗種別(親) 品名(子) 価格(子) 1 A店 コンビニ 1-1 ○○ 100 1-2 △△ 200 1-3 ☓☓ 300 2 B店 ドラッグストア 2-1 ○○ 100 2-2 △△ 200 →親Docはfqの店舗種別:コンビニで絞りこまれる “A店”だけがヒットするようになる q={!parent which=DocType:店舗} 品物:○○ &fq=店舗種別:コンビニ
  64. 64. Block-Joinの気になるところ (3) Block-Join Queryでは検索軸に指定した 親/子Docの絞り込みができない 補足: FilterQueryは親子Docの区別なく フィルタリングしてDocを抽出する
  65. 65. Block-Joinの気になるところ (3) Block-Join Queryでは検索軸に指定した 親/子Docの絞り込みができない 補足: FilterQueryは親子Docの区別なく フィルタリングしてDocを抽出する →検索軸が親/子どちらの場合でも使用できる。 (検索軸側に対して、本来のFilterCache用途で 使用することもできる)
  66. 66. Block-Joinの気になるところ (4) 検索軸に使用した親/子DocのFieldから Facetを取得することが出来ない
  67. 67. Block-Joinの気になるところ (4) 検索軸に使用した親/子DocのFieldから Facetを取得することが出来ない ・Facetは検索結果から作成するため
  68. 68. Block-Joinの気になるところ (4) 検索軸に使用した親/子DocのFieldから Facetを取得することが出来ない ・Facetは検索結果から作成するため →親/子Doc相互でFacet用に情報を持たせておく (複数の子→親ならマルチFieldなど) →子Docを検索軸にした場合の対応については パッチが提案されている。 https://issues.apache.org/jira/browse/SOLR-5743
  69. 69. Block-Joinの気になるところ (5) Block-Join Queryでは スコア計算は全て0.0になる
  70. 70. Block-Joinの気になるところ (5) Block-Join Queryでは スコア計算は全て0.0になる ・Lucene内では スコア計算のパターンはいくつか実装されている しかし…… 検索軸 タイプ 意味 子Doc None スコア計算なし(デフォルト) Avg 子Docのスコア平均 Max 子Docのスコア最大値 Total 子Docのスコア合計 親Doc false スコア計算なし(デフォルト) true 親Docのスコアを使う
  71. 71. Block-Joinの気になるところ (5) Block-Join Queryでは スコア計算は全て0.0になる ・Solr上のBlock-JoinQueryパーサ処理にて None(false)で固定でされている <BlockJoinParentQParser.java> protected Query createQuery(Query parentList, Query query) { return new ToParentBlockJoinQuery ( query, getFilter(parentList), ScoreMode.None ); }
  72. 72. Block-Joinの気になるところ (5) Block-Join Queryでは スコア計算は全て0.0になる ・Solr上のBlock-JoinQueryパーサ処理にて None(false)で固定でされている <BlockJoinParentQParser.java> ソースコードをいじらない限り Block-JoinQueryの結果でスコア取得する手段はない protected Query createQuery(Query parentList, Query query) { return new ToParentBlockJoinQuery ( query, getFilter(parentList), ScoreMode.None ); }
  73. 73. 終章. 進撃のBlock-Join
  74. 74. まとめ • Block-Joinは 「検索軸」を明確に分けるための機能
  75. 75. まとめ • Block-Joinは 「検索軸」を明確に分けるための機能 →当初の課題であった マスタ情報とサブ情報の 検索・更新軸を明示的に分けるという 用途では使えそうな気がする。
  76. 76. まとめ • Block-Joinは 「検索軸」を明確に分けるための機能 →当初の課題であった マスタ情報とサブ情報の 検索・更新軸を明示的に分けるという 用途では使えそうな気がする。 ・サブの要素数に左右されにくくなるので 複雑なクエリをスマートにしやすい、とか Field設計を容易にしやすい、とか……。
  77. 77. 課題 ・Block-Join Queryがクセモノ 要件によっては、ここまで述べてきた通り ひと工夫が必要になる。 ・パフォーマンスについては未知数……。
  78. 78. 課題 商用サイトでの導入と実証検証を目指して 引き続き調査・対応を進めていきます。 ・Block-Join Queryがクセモノ 要件によっては、ここまで述べてきた通り ひと工夫が必要になる。 ・パフォーマンスについては未知数……。
  79. 79. Q&A ご清聴ありがとうございました。 お問い合わせ先・エンジニア募集中 search-info@ditgroup.jp http://www.ditgroup.jp/

×