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

Hatena Engineer Seminar #14 GraphQL編

Hatena Engineer Seminar #14 GraphQL編

サブタイトル: 〜ユーザーも開発者も快適なサービスであるために〜

魔法のiらんどでは、クライアント・サーバー間のインターフェースとしてGraphQLを採用しました。この技術選択は、はてなの技術スタックのモダン化を進め、ユーザーと開発に携わるエンジニア双方の体験が快適であることを目指したものです。この発表では、魔法のiらんどリニューアルを成功に導いたGraphQLの強みと、月間3億PVをさばくためのパフォーマンス面の検討について紹介します。

Yuu Igarashi

July 15, 2020
Tweet

More Decks by Yuu Igarashi

Other Decks in Programming

Transcript

  1. ࣗݾ঺հ • id:yigarashi • ৽ଔ2೥໨ • WebΞϓϦέʔγϣϯΤϯδχΞ • ڵຯ •

    ։ൃϓϩηεɺϓϩδΣΫτ؅ཧ • ϓϩάϥϛϯάݴޠཧ࿦
  2. GraphQLͱ͸ { me { name interests } } ΫΤϦ {

    "me": { "name": "yigarashi", "interests": [ "։ൃϓϩηε", "ϓϩάϥϛϯάݴޠཧ࿦", ] } } ฦΓ஋ʢ+40/ʣ "1*ͷͨΊͷΫΤϦݴޠͱΫΤϦʹԠͨ͡σʔλΛฦ͢ϥϯλΠϜ ΫϥΠΞϯτ αʔόʔ ʢϥϯλΠϜʣ
  3. GraphQLͱ͸ { me { name interests } } ΫΤϦ {

    "me": { "name": "yigarashi", "interests": [ "։ൃϓϩηε", "ϓϩάϥϛϯάݴޠཧ࿦", ] } } ฦΓ஋ʢ+40/ʣ ΫΤϦͱಉ͡ܗͷ +40/͕ฦ͞ΕΔ "1*ͷͨΊͷΫΤϦݴޠͱΫΤϦʹԠͨ͡σʔλΛฦ͢ϥϯλΠϜ ΫϥΠΞϯτ αʔόʔ ʢϥϯλΠϜʣ
  4. ॏཁͳ֓೦(1) - εΩʔϚ ϥϯλΠϜ͕ͲΜͳσʔλΛฦͤΔ͔هड़ͨ͠΋ͷ type Query { me: Me }

    type Me { name: String interests: List[String] } εΩʔϚͷྫ αʔόʔ ʢϥϯλΠϜʣ ΫϥΠΞϯτ
  5. ॏཁͳ֓೦(1) - εΩʔϚ ϥϯλΠϜ͕ͲΜͳσʔλΛฦͤΔ͔هड़ͨ͠΋ͷ type Query { me: Me }

    type Me { name: String interests: List[String] } εΩʔϚͷྫ αʔόʔ ʢϥϯλΠϜʣ ΫϥΠΞϯτ { me { name interests } }
  6. ॏཁͳ֓೦(1) - εΩʔϚ ϥϯλΠϜ͕ͲΜͳσʔλΛฦͤΔ͔هड़ͨ͠΋ͷ type Query { me: Me }

    type Me { name: String interests: List[String] } εΩʔϚͷྫ αʔόʔ ʢϥϯλΠϜʣ ΫϥΠΞϯτ { me { name interests } }
  7. type Query { me: Me } type Me { name:

    String interests: List[String] } ॏཁͳ֓೦(1) - εΩʔϚ ϥϯλΠϜ͕ͲΜͳσʔλΛฦͤΔ͔هड़ͨ͠΋ͷ εΩʔϚͷྫ αʔόʔ ʢϥϯλΠϜʣ ΫϥΠΞϯτ
  8. type Query { me: Me } type Me { name:

    String interests: List[String] } ॏཁͳ֓೦(1) - εΩʔϚ ϥϯλΠϜ͕ͲΜͳσʔλΛฦͤΔ͔هड़ͨ͠΋ͷ εΩʔϚͷྫ αʔόʔ ʢϥϯλΠϜʣ ΫϥΠΞϯτ { me { name interests age } }
  9. type Query { me: Me } type Me { name:

    String interests: List[String] } ॏཁͳ֓೦(1) - εΩʔϚ ϥϯλΠϜ͕ͲΜͳσʔλΛฦͤΔ͔هड़ͨ͠΋ͷ εΩʔϚͷྫ αʔόʔ ʢϥϯλΠϜʣ ΫϥΠΞϯτ { me { name interests age } }
  10. ॏཁͳ֓೦(2) - Ϧκϧό ϥϯλΠϜʹ͓͍ͯϑΟʔϧυͷ஋Λܭࢉ͢Δؔ਺ { me { name interests }

    } • meΛܭࢉ͢ΔϦκϧόΛ࣮ߦ • DB͔ΒmeʹରԠ͢ΔΦϒδΣΫτMeΛऔಘ యܕతͳ࣮ߦ
  11. ॏཁͳ֓೦(2) - Ϧκϧό ϥϯλΠϜʹ͓͍ͯϑΟʔϧυͷ஋Λܭࢉ͢Δؔ਺ { me { name interests }

    } • meΛܭࢉ͢ΔϦκϧόΛ࣮ߦ • DB͔ΒmeʹରԠ͢ΔΦϒδΣΫτMeΛऔಘ • nameΛܭࢉ͢ΔϦκϧόΛ࣮ߦ • Me.nameΛฦ͢ యܕతͳ࣮ߦ
  12. ॏཁͳ֓೦(2) - Ϧκϧό ϥϯλΠϜʹ͓͍ͯϑΟʔϧυͷ஋Λܭࢉ͢Δؔ਺ { me { name interests }

    } • meΛܭࢉ͢ΔϦκϧόΛ࣮ߦ • DB͔ΒmeʹରԠ͢ΔΦϒδΣΫτMeΛऔಘ • nameΛܭࢉ͢ΔϦκϧόΛ࣮ߦ • Me.nameΛฦ͢ • interestsΛܭࢉ͢ΔϦκϧόΛ࣮ߦ • Me.interests͕idͷྻʹͳ͍ͬͯΔͷͰDB͔Βऔಘ͢Δ యܕతͳ࣮ߦ
  13. ॏཁͳ֓೦(2) - Ϧκϧό ϥϯλΠϜʹ͓͍ͯϑΟʔϧυͷ஋Λܭࢉ͢Δؔ਺ { me { name interests }

    } • meΛܭࢉ͢ΔϦκϧόΛ࣮ߦ • DB͔ΒmeʹରԠ͢ΔΦϒδΣΫτMeΛऔಘ • nameΛܭࢉ͢ΔϦκϧόΛ࣮ߦ • Me.nameΛฦ͢ • interestsΛܭࢉ͢ΔϦκϧόΛ࣮ߦ • Me.interests͕idͷྻʹͳ͍ͬͯΔͷͰDB͔Βऔಘ͢Δ • ࠷ޙʹ׬੒ͨ͠஋ΛJSONʹͯ͠ฦ͢ యܕతͳ࣮ߦ
  14. Ϣʔβʔମݧɾ։ൃޮ཰Λ޲্͢Δ • Ϣʔβʔମݧ • SPA(Single Page Application) + APIͷߏ੒ʹ͢Δ͜ͱͰɺϞμϯͳ ΞϓϦతͳମݧΛ࣮૷͠΍͘͢͢Δ

    • ։ൃޮ཰ • ΫΤϦݴޠ͕ڧྗͰը໘ͷมߋʹڧ͍ • Ϟμϯͳٕज़Λ࠾༻͢Δ͜ͱͰϞνϕʔγϣϯ޲্ • ͔ͦ͠͠Ε͚ͩͰ͸ͳ͍……
  15. REST API ͸ͭΒ͍ sub example_handler { my $common_field = get_common_field;

    if (is_kakuyomu) { $common_field = kakuyomu_specific_operation($common_field); } my $kakuyomu_field = undef; if (is_kakuyomu) { $kakuyomu_field = get_kakuyomu_field; } ... } ίΞϩδοΫΛ࢖ͬͯJSON Λฦ͢෦෼ͷංେԽɾΧΦ εԽ͕༧૝͞Εͨ
  16. REST API ͸ͭΒ͍ sub example_handler { my $common_field = get_common_field;

    if (is_kakuyomu) { $common_field = kakuyomu_specific_operation($common_field); } my $kakuyomu_field = undef; if (is_kakuyomu) { $kakuyomu_field = get_kakuyomu_field; } ... } ίΞϩδοΫΛ࢖ͬͯJSON Λฦ͢෦෼ͷංେԽɾΧΦ εԽ͕༧૝͞Εͨ ڞ௨ͷϑΟʔϧυ͕ͩΧΫϤϜͰ ͸ಛघͳՃ޻͕ඞཁ
  17. REST API ͸ͭΒ͍ sub example_handler { my $common_field = get_common_field;

    if (is_kakuyomu) { $common_field = kakuyomu_specific_operation($common_field); } my $kakuyomu_field = undef; if (is_kakuyomu) { $kakuyomu_field = get_kakuyomu_field; } ... } ίΞϩδοΫΛ࢖ͬͯJSON Λฦ͢෦෼ͷංେԽɾΧΦ εԽ͕༧૝͞Εͨ ڞ௨ͷϑΟʔϧυ͕ͩΧΫϤϜͰ ͸ಛघͳՃ޻͕ඞཁ ΧΫϤϜͷΈଘࡏ͢ΔϑΟʔϧυ
  18. GraphQLͰղܾ GraphQL::Type::Object->new( name => 'Example', fields => +{ common_field =>

    is_kakuyomu ? kakuyomu_resolver : maho_resolver, (is_kakuyomu ? ( kakuyomu_field => kakuyomu_field_resolver ) : ()) ... } ) ϑΟʔϧυ͝ͱʹϦκϧ όΛఆٛ͢ΔͷͰɺॲཧ ͷৼΓ෼͚Λॊೈ͔ͭ៉ ྷʹߦ͏͜ͱ͕Ͱ͖Δ
  19. GraphQLͰղܾ GraphQL::Type::Object->new( name => 'Example', fields => +{ common_field =>

    is_kakuyomu ? kakuyomu_resolver : maho_resolver, (is_kakuyomu ? ( kakuyomu_field => kakuyomu_field_resolver ) : ()) ... } ) ϑΟʔϧυ͝ͱʹϦκϧ όΛఆٛ͢ΔͷͰɺॲཧ ͷৼΓ෼͚Λॊೈ͔ͭ៉ ྷʹߦ͏͜ͱ͕Ͱ͖Δ ݺͼग़͢ϦκϧόΛৼΓ෼͚Δͩ ͚Ͱྑ͍
  20. GraphQLͰղܾ GraphQL::Type::Object->new( name => 'Example', fields => +{ common_field =>

    is_kakuyomu ? kakuyomu_resolver : maho_resolver, (is_kakuyomu ? ( kakuyomu_field => kakuyomu_field_resolver ) : ()) ... } ) ϑΟʔϧυ͝ͱʹϦκϧ όΛఆٛ͢ΔͷͰɺॲཧ ͷৼΓ෼͚Λॊೈ͔ͭ៉ ྷʹߦ͏͜ͱ͕Ͱ͖Δ ݺͼग़͢ϦκϧόΛৼΓ෼͚Δͩ ͚Ͱྑ͍ αʔϏε͝ͱʹඞཁͳϑΟʔϧυ ͚ͩ࿐ग़͢Δ
  21. Ըܙ(1) - ϑϩϯτΤϯυ։ൃମݧͷ޲্ • ύϫϑϧͳΫΤϦݴޠʹΑΔॊೈͳσʔλऔಘ • Τϐιʔυ --> ࡞඼ -->

    ࡞ऀͱ͍ͬͨωετͨ͠σʔλ΋αʔόʔ ͷมߋͳ͠ʹ؆୯ʹऔಘͰ͖Δ • Apollo ClientͷԸܙ • Reactͱ࿈ܞ͢ΔGraphQLΫϥΠΞϯτͷσϑΝΫτ • ߥ࡟Γͳ෦෼΋͋Δ͕ϞμϯͳUIΛ࡞ΔͨΊͷػೳ͕๛෋
  22. Ըܙ(2) - API։ൃͷޮ཰Խ • ࠷ॳʹશͯͷը໘Λਫ਼ࠪͯ͠1000ߦ͍ۙεΩʔϚΛઃܭͨ͠ • εΩʔϚϑΝʔετͳ։ൃ • εΩʔϚઃܭΛ௨ͯ͠ϓϩμΫτ΁ͷཧղ͕ਂ·ͬͨ •

    ΤϯδχΞ8ਓͷେॴଳ͕͏·͘ػೳ͢Δ͖͔͚ͬʹ • εΩʔϚத৺ͷίϛϡχέʔγϣϯͰԿΛ࡞Δ͔ͷೝ͕ࣝἧͬͨ • type ΍ mutation ୯Ґͷ෼୲Ͱߴ౓ͳฒྻ։ൃ͕Մೳʹ
  23. ࡞ઓ • N+1໰୊ͷରࡦͳͲجຊతͳ͜ͱ͸΍Δ • Ωϟογϡ͕೉͍͠ͷΛͲ͏͢Δ͔ • ΤϯυϙΠϯτ͕ /graphql ʹू໿͞Εͯ͠·͏ •

    ΞϓϦέʔγϣϯϨΠϠʔͰͷΩϟογϡ͸࣮૷ίετ͕ߴ͍ • ຊ౰ʹΩϟογϡ͕ඞཁ͔ෛՙࢼݧΛߦͬͯ໌Β͔ʹ͢Δ • ਪଌ͢ΔͳɺܭଌͤΑ
  24. ෛՙࢼݧ - 1ճ໨ͷ݁Ռ • Ωϟογϡͱؔ܎ͳ͘ϨΠςϯγ͕ҟৗʹѱ͍ • ͔͠΋ϨεϙϯεαΠζʹൺྫͯ͠ϨΠςϯγ͕ѱԽ͍ͯ͠Δ… • ϓϩϑΝΠϦϯάͯ͠ௐࠪ •

    ࣮ߦ࣌ͷܕνΣοΫϥΠϒϥϦ͕ɺ֤ϑΟʔϧυͷϦκϧόݺͼग़ ͠͝ͱʹݺ͹Ε͍ͯͨͷ͕ݪҼ • production؀ڥͰܕνΣοΫΛແޮʹ͢ΔύονΛ౰ͯͯ࠶τϥΠ
  25. ෛՙࢼݧ - 2ճ໨ͷ݁Ռ • ര଎ • ฏۉϨΠςϯγ͸100msۙ͘ • ݱ࣮తͳΠϯϑϥͰຊ൪૝ఆΛେ্͖͘ճΔෛՙʹ଱͑ΒΕͨ •

    ݁࿦: Ωϟογϡͳ͠ͰϦϦʔεޙͷෛՙʹ଱͑ΒΕΔ • ϦϦʔεޙɺෛՙͷ໰୊ͰαʔϏεμ΢ϯ͢Δ͜ͱ͸ͳ͔ͬͨ • ܭଌʹΑͬͯඞཁे෼ͳରࡦΛߦ͑ͨ
  26. • ຐ๏ͷiΒΜͲͰ͸ GraphQL Λ࠾༻͠·ͨ͠ɹ • ϞμϯͳϢʔβʔମݧͷ࣮ݱʹߩݙ • ։ൃޮ཰ɾ։ൃϞνϕʔγϣϯ͕޲্ • ΧΫϤϜͱͷϩδοΫڞ༗ͱ͍͏೉͍͠ཁٻʹϚον

    • ύϑΥʔϚϯε໘΋ཱ྆ • ͸ͯͳͱGraphQL • ϋϚΔͱ͜ΖͰ࠾༻͞ΕΔ͜ͱ͕૿͖͑ͯͨ • ͜Ε͔Β΋ϓϥΫςΟεͷ஝ੵɾڞ༗ΛਐΊ͍͖ͯ·͢ʂ