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.

10年目の『エブリスタ』を支える技術

7,804 views

Published on

まもなく10年目を迎える小説投稿プラットフォーム『エブリスタ』。
この10年で業界は大きく変化し、本サービスもリニューアルという大きな転換期を迎えました。
10年という歳月によりシステム全体もレガシー化し、不要なモジュール、増え続ける機能、機能の変化によるテーブル構造の不都合やパフォーマンス課題など様々な課題が浮き彫りとなってきました。
本セッションではこれらの課題を解決すべく実施したリニューアルのプロセスやその現場におけるKubernetesやGraphQLといった新技術の活用を事例を交えご紹介します。

Published in: Technology
  • Be the first to comment

  • Be the first to like this

10年目の『エブリスタ』を支える技術

  1. 1. #denatechcon #denatechcon 10年目の 『エブリスタ』 を支える技術 井田 祐太 / 松尾 卓朗 ゲーム・エンターテインメント事業本部IPプラットフォーム事業部サービス開発部
  2. 2. #denatechcon 開発期間約1年半
  3. 3. #denatechcon 2019年春 エブリスタは 生まれ変わります
  4. 4. #denatechcon #denatechcon 10年目の 『エブリスタ』リニューアル を支える技術 井田 祐太 / 松尾 卓朗 ゲーム・エンターテインメント事業本部IPプラットフォーム事業部サービス開発部
  5. 5. #denatechcon 自己紹介 - 井田 祐太 - @da_ponta - https://note.mu/fablab - 2017年4月中途入社 - エブリスタの開発全般を担当 - Perl - Ruby - node.js - Android - Kubernetes 普段はテラリウムなどものづくり全般やってます
  6. 6. #denatechcon エブリスタとは だれもが作家になれる 小説投稿サイト
  7. 7. #denatechcon 代表作品
  8. 8. #denatechcon サービスの歴史 2007年3月 2010年6月 2019年春 モバゲー 小説コーナー エブリスタ オープン システム リニューアル予定 10年目突入!
  9. 9. #denatechcon 10年めのサービスの負債 - 誰も把握していない仕様 - コピペされたコード - 独自フレームワーク - 未知のテーブル、カラム - 存在しないテスト、ドキュメント - SJIS - フィーチャーフォン - 4,5種類存在するUI ...
  10. 10. #denatechcon 闇が深い… 作り直すしかない
  11. 11. #denatechcon リニューアル対象: DB・サーバサイド・インフラ・アプリ UI・UX・フロントエンド・機能 ≒システムすべて
  12. 12. #denatechcon リニューアルに際しての課題 - エンジニアリソース(3〜4人😰) - 機能数(コントローラ数換算:900, テーブル数:1000) - 既存サービスの運用も継続しなければならない - リリース後も高速な開発サイクルにしたい
  13. 13. #denatechcon アプリケーション編
  14. 14. #denatechcon アジェンダ - GraphQLの活用 - kubernetesをフル活用した開発環境
  15. 15. #denatechcon サービス群 MAIN API GraphQL COMICPAYMENTAUTH FRONT NOTIFICATION USER OPE BOX
  16. 16. #denatechcon GraphQLの採用理由 リリース後も開発サイクルを高速で回したい
  17. 17. #denatechcon GraphQLのリニューアルにおける利点 - UI要件の変化への対応が不要 →開発速度UP - ドキュメント作成が不要 →工数DOWN - コードがクリーンに保てる →工数増加防止 - モチベーション向上と2年間の維持ができた →開発速度維持
  18. 18. #denatechcon UI要件の変化への対応が不要 query($userId: ID!) { user(userId:$userId) { nickname introduction } } ※画面は開発中のものです 😅<ここに作品をいれたいんだ けど…
  19. 19. #denatechcon UI要件の変化への対応が不要 query($userId: ID!) { user(userId:$userId) { nickname introduction novels { title updated_at ... } } } ※画面は開発中のものです
  20. 20. #denatechcon ドキュメント作成いらず # 型定義 class ContestType < Types::BaseObject # 型の名称 graphql_name "Contest" # 型の説明文 description "公式イベント" # 取得できるデータ field :contest_id, ID, "イベントID", null: false end # Queryの定義 class FindContest < Resolvers::Base # 返す型 type ContestType, null: false # クエリの説明 description "公式イベント" argument :contest_id, ID, "公式イベントID", required: true ... end
  21. 21. #denatechcon GraphiQL # Queryの定義 class FindContest < Resolvers::Base # 返す型 type ContestType, null: false # クエリの説明 description "公式イベント" argument :contest_id, ID, "公式イベントID", required: true ... end
  22. 22. #denatechcon GraphiQL
  23. 23. #denatechcon GraphiQLだと少し足りない query($userId: ID!) { user(userId:$userId) { nickname introduction novels { title updated_at ... } } } novelsはどの型が持 っているんだ?
  24. 24. #denatechcon GraphQL Voyager
  25. 25. #denatechcon Railsあるある(?) - アソシエーションとビジネスロジックでファットなモデル - ビュー処理とモデルの呼び出しでファットなコントローラ ▶ graphql(-ruby)だとスッキリ!
  26. 26. #denatechcon エブリスタにおけるアーキテクチャ models モデルに閉じたビジネスロジック policies 権限の判定ロジック repositories DB以外も含めた外部リソース操作ロジック services 何らかの一連のビジネスロジック graphql コントローラ or ViewModel
  27. 27. #denatechcon Modelの関連をGraphQLが吸収 - 返却するデータ構造の構築が不要 - model・controllerに複雑なロジックを書く必要がない - 意識せずにテーブルと対応したnull安全なデータに # User has a novel class UserType field :novel, NovelType, null: false ... end # Novel belongs to user class NovelType field :user, UserType, null: true ... end
  28. 28. #denatechcon モチベーションについて① - Github、Facebookが採用 - 採用事例増加中 - 新しい技術は楽しい
  29. 29. #denatechcon モチベーションについて② 開発効率が良くないと乗り切れない!! - プロジェクト期間:2年 - エンジニア数:3〜4人 - 機能数:コントローラ数ベース:900 - テーブル数:1000 - 作りながら要件は変化・増加する
  30. 30. #denatechcon わずか2,3行でAPIにデータが追加される class Types::UserType < Types::BaseObject description "ユーザ" ... field :followers, Types::UserType.connection_type,"フォロワー", null: false, connection: true def followers(**args) @object.follower_users end end class User < ApplicationRecord self.primary_key = :user_id has_many :followers has_many :follower_users, through: :followers ... end
  31. 31. #denatechcon ②novel経由で取得するクエリ ①user経由で取得するクエ リ
  32. 32. #denatechcon ハマりどころも - エラーハンドリングはどう行うか? - フロントエンドから無制限にデータを参照されるのでは? - パフォーマンス面(N+1問題など)
  33. 33. #denatechcon デフォルトのエラー形式 query { user(userId:"存在しないID") { nickname } } { "data": null, "errors": [ { "message": "User not found.", # システムメッセージ "locations": [ # クエリにおける発生行 { "line": 2, "column": 3 } ], "path": [ # クエリにおける発生階層 "user" ], } ] } →メッセージとエラーの発生した階層だけなのでクライアントでのハンドリングが困難
  34. 34. #denatechcon graphql-errors + エラーカスタム class ApiError < GraphQL::ExecutionError def initialize(message, type = nil) @type = type super(message) end def to_h super.merge("type" => @type) end end graphql-errors https://github.com/exAspArk/graphql-errors GraphQL::Errors.configure(schema) do rescue_from ActiveRecord::RecordNotFound do |e| raise ApiError.new(“#{e.model} not found”, "NotFound") end # 独自エラークラス rescue_from Errors::PermissionDenied do |e| raise ApiError.new(e.message, e.class.to_s) end end
  35. 35. #denatechcon 拡張されたエラー形式 query { user(userId:"存在しないID") { nickname } } { "data": null, "errors": [ { "message": "User not found.", "locations": [ { "line": 2, "column": 3 } ], "path": [ "user" ], "type": "NotFound" } ] } →パターン化された`type`によりエラーハンドリングが可能に
  36. 36. #denatechcon クエリの実行制限 {query: “findNovel.gql”}
  37. 37. #denatechcon 開発環境
  38. 38. #denatechcon 本番と最小限の差異の開発環境 - minikubeを使ったローカルkubernetes - helmによるテンプレート化 - ドメインの割当 - SSL化
  39. 39. #denatechcon kubernetesを活用した開発環境 - minikubeを利用 - minikubeからNFSマウント - minikubeからk8sにコード共有
  40. 40. #denatechcon podへのソースコードマウント(抜粋) $ sudo nfsd start $ IP=$(minikube ip | awk -F"." '{print $1"."$2"."$3".1"}') $ minikube ssh -- sudo mount -t nfs $IP:/path/to/src /src -o rw,async,noatime,rsize=32768,wsize=32768,proto=tcp containers: - name: api image: "api:v1" volumeMounts: - mountPath: "/opt/api" name: "api-volume" volumes: - name: "api-volume" hostPath: path: "/src/api" その他の手法 Tilt,Skaffold:ビルドが発生するのでその分の遅延有り Docker for Mac:マウントするファイル数が多くなると参照エラーが発生し断念 Dockerイメージは/opt/apiを WORKDIRに指定します
  41. 41. #denatechcon Helmによるテンプレート化(抜粋) - レプリカ数 - Dockerイメージのリポジトリ - 起動コマンドの引数 - configMapの数、種類 spec: replicas: {{ .Values.replica }} template: spec: containers: - name: estar-api image: “{{.Values.REPO}}/api:latest" args: [ {{- range .Values.args }} "{{ . }}", {{- end }} ] envFrom: {{- range .Values.args }} - configMapRef: name: {{ . }} {{- end }}
  42. 42. #denatechcon まとめ - エンジニアリソースの課題 → GraphQLにより実装が簡易化されエンジニア体制が最適化 → minikubeにより環境整備の工数削減 - 900の機能数 → GraphQLによりAPI開発数の減少(150クエリに) - リリース後も開発サイクルを高速で回したい → フロントエンド主導の開発プロセスが実現
  43. 43. #denatechcon ❕注意事項 minikubeを利用するには高スペックなPCが必要です 必要要件(エブリスタチームの場合) - memory 16GB - クアッドコア
  44. 44. #denatechcon クラウド活用とサービス開発
  45. 45. #denatechcon 自己紹介 松尾卓朗 •2017年新卒 DeNA入社(2年目) • IPPF事業部 • サーバーサイド、クラウドを担当
  46. 46. #denatechcon 自己紹介 気づいたら、DevOpsの魅力に惹 かれる • SRE • カオスエンジニアリング • CI/CD
  47. 47. #denatechcon 役割の分割 コミック Web小説サービス 小説 認証 通知 課金 コミック スゴ得 コンテンツ ステークホルダーとのサービス
  48. 48. #denatechcon 役割の分割 コミック Web小説サービス 小説 認証 通知 課金 コミック 開発進めたいのはココ スゴ得 コンテンツ ステークホルダーとのサービス
  49. 49. #denatechcon サービス群 MAIN API GraphQL COMICPAYMENTAUTH BFF NOTIFICATION USER OPE BOX
  50. 50. #denatechcon モノリスとサービシーズ MAIN API GraphQL COMICPAYMENTAUTH FRONT NOTIFICATION OPE BOX開発者は小説の開発に 集中できる。 モノリス サービシーズ
  51. 51. #denatechcon クラウド
  52. 52. #denatechcon 今までの運用体制 サービスエンジニア インフラエンジニア インフラは 全てお任せ
  53. 53. #denatechcon これからの運用体制 サービスエンジニア インフラエンジニア インフラはほぼ全て 事業部エンジニアが
  54. 54. #denatechcon ですが
  55. 55. #denatechcon リソースが足りない、、、 事業部エンジニア ● アプリケーション開発 ● インフラ構築 ● ネイティブアプリ開発
  56. 56. #denatechcon 最小限の工数でクラウドを活用する。 • インフラの管理コストを削減 • アプリケーションエンジニアがクラウド業務を 兼務できるように • 実行環境差分を少なくする
  57. 57. #denatechcon クラウド環境 Amazon SES Amazon ElasticSearch Service
  58. 58. #denatechcon Amazon SES Amazon ElasticSearch Service クラウド環境 kubernetes が分かっていれば、 クラウド環境も理解できる を目指す
  59. 59. #denatechcon 最小限の工数でクラウドを活用する。 • インフラの管理コストを削減 • アプリケーションエンジニアがクラウド業務を 兼務できるように • 実行環境差分を少なくする
  60. 60. #denatechcon クラウドの管理 GCP • メモリー • CPU • アプリケーションサービスのノード数 • インスタンスのライフサイクル • ロードバランサ • ネットワーク周り • Cluster nodeの管理 • 各種ミドルウェア
  61. 61. #denatechcon Infra as a codeはどこまで? すでにcode化されている code化しない GCP • メモリー • CPU • アプリケーションサービスのノード数 • インスタンスのライフサイクル • ロードバランサ • ネットワーク周り • Cluster nodeの管理 • 各種ミドルウェア
  62. 62. #denatechcon マネージド以外は使わない Prometheu s prometheus
  63. 63. #denatechcon 最小限の工数でクラウドを活用する。 • インフラの管理コストを削減 • アプリケーションエンジニアがクラウド業務を 兼務できるように • 実行環境差分を少なくする
  64. 64. #denatechcon アプリケーションエンジニアのインフラ兼務 Local MiniKube GKE GCP Production …etc この差分をカバー さえすれば、 ほぼ運用できる。 2 1.5学習コスト比
  65. 65. #denatechcon クラウド環境 • コンテナ • ログの収集 • 監視 • ネットーワーク • デプロイ • データコンバート
  66. 66. #denatechcon コンテナ ●1コンテナ1プロセス ●プロセスの管理を抽象化 ●Dockerの管理= Railsの管理 ●各環境でも同じコンテナ ●Ubuntu ベース
  67. 67. #denatechcon Pod(kubernetes)
  68. 68. #denatechcon ログの収集 • Cluster nodeのメモリ,CPU ..etc • コンテナのメモリ,CPU ..etc
  69. 69. #denatechcon 外形監視 • 基本はすべて、 stackdriverで監視を行う • 外形監視のみmonitis 監視
  70. 70. #denatechcon GCP ネットワーク USER Cloud NAT
  71. 71. #denatechcon CI/CD Deploy Server デプロイ DeNA
  72. 72. #denatechcon データのコンバート onPremis Mysql Perl GCS転送スクリプト JSON gsutil rsync GCS受信スクリプト 受信 処理済み Rubyコンバート スクリプト gsutil rsync 差分
  73. 73. #denatechcon 2019年春 新しいエブリスタ始まる
  74. 74. #denatechcon #denatechcon

×