Java が支える 人気ニュースアプリ NewsPicks の裏側

Bf9540996d162846c723821a111c673d?s=47 monzou
April 11, 2015

Java が支える 人気ニュースアプリ NewsPicks の裏側

JJUG CCC 2015 Spring の発表資料です。

Bf9540996d162846c723821a111c673d?s=128

monzou

April 11, 2015
Tweet

Transcript

  1. Java が支える 人気ニュースアプリ NewsPicks の裏側 2015.04.11 JJUG CCC 2015 SPRING

    Takuro MONJI @ UZABASE, Inc.
  2. UZABASE, Inc. ABOUT ME Takuro MONJI @monzou 2014 年 9

    月に UZABASE にジョイン 前職は金融系トレーディングシステム開発
  3. NewsPicks, Inc. ABOUT ME Takuro MONJI @monzou 2014 年 9

    月に UZABASE にジョイン 前職は金融系トレーディングシステム開発
  4. 昨年も講演させて頂きました https://speakerdeck.com/monzou/spa-development

  5. AGENDA 1. UZABASE / NewsPicks について 2. NewsPicks の裏側 3.

    急成長を支える技術と舞台裏
  6. AGENDA 1. UZABASE / NewsPicks について 2. NewsPicks の裏側 3.

    急成長を支える技術と舞台裏
  7. ABOUT 2008 年 4 月 創業 従業員数 160 名(アルバイト含む) (NewsPicks

    は 40 名弱) 事業拠点 ・東京 ・シンガポール ・上海 ・香港 ミッション 「世界一の経済メディアをつくる」
  8. 世界一の経済メディア SPEEDA 高度な分析を行うためのインフラ 経済情報との接点 NewsPicks 新たなビジネス情報を発見するためのインフラ プロフェッショナル向け 世界展開中 一般ビジネスパーソン向け まずは国内

    No.1 を目指す
  9. None
  10. = ビジネスパーソン向け 経済特化型ニュースアプリ

  11. = ビジネスパーソン向け 経済特化型ニュースアプリ 様々な経済情報をワンストップで提供 著名人・専門家によるキュレーション / コメント ビジネスパーソン向けオリジナルコンテンツ

  12. =

  13. =

  14. None
  15. = PLATFORM + PUBLISHER

  16. PLATFORM PUBLISHER キュレーション コンテンツ 多様な 経済情報 ビジネス パーソン向け 独自記事 ブランド

    広告記事 PICKER キュレーション・コメント ... ... ... 厳選提携メディア 広告主 外部サイト 編集部
  17. 他ニュースアプリと比較した場合の特徴 機能面 経済特化 コメント 有料課金 独自記事 記事編成 ユーザー特性 意思決定者層が多い 著名人/専門家が多い

    滞在時間が非常に長い 熱心なファンが多い 拡散力が強い
  18. サービスの成長 ユーザー数 有料課金数 ユーザー数も 35 万人を超え、 有料課金ユーザー数も順調に増加中 (エンジニアも最近ようやく増えてきました ……)

  19. チーム体制 プラットフォーム コンテンツ x テクノロジーの融合 EDITOR x ENGINEER ENGINEER x

    DESIGNER DESIGNER x EDITOR ... ブランド デザイン 編集部
  20. チーム体制 プラットフォーム 各チームが緊密に連携しながら開発中 プラットフォーム(エンジニア)は 現在 10 名程度 ブランド デザイン 編集部

    記事編成 / リコメンド / プッシュ通知 など
  21. チーム体制 プラットフォーム 各チームが緊密に連携しながら開発中 プラットフォーム(エンジニア)は 現在 10 名程度 ブランド デザイン 編集部

    記事編成 / リコメンド / プッシュ通知 など 編集長です
  22. チーム体制 プラットフォーム 各チームが緊密に連携しながら開発中 プラットフォーム(エンジニア)は 現在 10 名程度 ブランド デザイン 編集部

    記事編成 / リコメンド / プッシュ通知 など 編集長です 社長です
  23. チーム体制 プラットフォーム 各チームが緊密に連携しながら開発中 プラットフォーム(エンジニア)は 現在 10 名程度 ブランド デザイン 編集部

    記事編成 / リコメンド / プッシュ通知 など 編集長です 社長です 編集部員です
  24. チーム体制 プラットフォーム 各チームが緊密に連携しながら開発中 プラットフォーム(エンジニア)は 現在 10 名程度 ブランド デザイン 編集部

    記事編成 / リコメンド / プッシュ通知 など 編集長です 社長です 編集部員です 職人の手による 温かみのあるプッシュ通知です
  25. チーム体制 プラットフォーム 各チームが緊密に連携しながら開発中 プラットフォーム(エンジニア)は 現在 10 名程度 ブランド デザイン 編集部

    NewsPicks Paper
  26. チーム体制 プラットフォーム ブランド デザイン 編集部 プロジェクト チーム グロース チーム

  27. チーム体制 プラットフォーム ブランド デザイン 編集部 プロジェクト チーム グロース チーム

  28. コンテンツとテクノロジーの融合で 世界一の経済メディアを目指す

  29. AGENDA 1. UZABASE / NewsPicks について 2. NewsPicks の裏側 3.

    急成長を支える技術と舞台裏
  30. Dynamo DB ElastiCache INTERNET DESKTOP PC MOBILE ROUTE 53 ELB

    EIP INCOMING APP BAT Elasticsearch RDS Redshift CloudWatch SNS SES SQS
  31. Dynamo DB ElastiCache INTERNET DESKTOP PC MOBILE ROUTE 53 ELB

    EIP INCOMING APP BAT Elasticsearch RDS Redshift CloudWatch SNS SES SQS
  32. Dynamo DB ElastiCache INTERNET DESKTOP PC MOBILE ROUTE 53 ELB

    EIP INCOMING APP BAT Elasticsearch RDS Redshift CloudWatch SNS SES SQS AWS OR DIE
  33. Dynamo DB ElastiCache INTERNET DESKTOP PC MOBILE ROUTE 53 ELB

    EIP INCOMING APP BAT Elasticsearch RDS Redshift CloudWatch SNS SES SQS DATABASE
  34. Dynamo DB ElastiCache INTERNET DESKTOP PC MOBILE ROUTE 53 ELB

    EIP INCOMING APP BAT Elasticsearch RDS Redshift CloudWatch SNS SES SQS DATABASE RDS : Master Data Dynamo DB : Transaction Data ElastiCache : Timeline / Ranking / Cache etc Redshift : Access Log / Analytics
  35. Dynamo DB ElastiCache INTERNET DESKTOP PC MOBILE ROUTE 53 ELB

    EIP INCOMING APP BAT Elasticsearch RDS Redshift CloudWatch SNS SES SQS
  36. Dynamo DB ElastiCache INTERNET DESKTOP PC MOBILE ROUTE 53 ELB

    EIP INCOMING APP BAT Elasticsearch RDS Redshift CloudWatch SNS SES SQS Java OR DIE
  37. Dynamo DB ElastiCache INTERNET DESKTOP PC MOBILE ROUTE 53 ELB

    EIP INCOMING APP BAT Elasticsearch RDS Redshift CloudWatch SNS SES SQS WHY Java ? SPEEDA SIMPLE Java 8 LOMBOK
  38. Dynamo DB ElastiCache INTERNET DESKTOP PC MOBILE ROUTE 53 ELB

    EIP INCOMING APP BAT Elasticsearch RDS Redshift CloudWatch SNS SES SQS WHY Java ? DYNAMIC TYPING TOO SLOW COMPILE
  39. INTERNET DESKTOP PC MOBILE ROUTE 53 ELB EIP INCOMING APP

    BAT Elasticsearch Java Applications CMS RECOMMEND ANALYTICS MANAGE DASHBOARD SUPPORT
  40. newspicks-categorizer newspicks-model newspicks-web-core newspicks-web newspicks-api newspicks-contents newspicks-support newspicks-cms newspicks-core newspicks-worker

    APPLICATION LAYER WEB LAYER DOMAIN LAYER INFRASTRUCTURE newspicks-service newspicks-recommender
  41. Dynamo DB ElastiCache INTERNET DESKTOP PC MOBILE ROUTE 53 ELB

    EIP INCOMING APP BAT Elasticsearch RDS Redshift CloudWatch SNS SES SQS APP SERVER
  42. ELB SQS Elasticsearch RDS (MySQL) ElastiCache (Redis) Dynamo DB DATABASE

    READ / WRITE バックエンドに 非同期タスクを登録 全文検索 重複判定 Redshift api.newspicks.com contents.newspicks.com newspicks.com Java 8 Spring MVC
  43. ELB SQS Elasticsearch RDS (MySQL) ElastiCache (Redis) Dynamo DB DATABASE

    READ / WRITE バックエンドに 非同期タスクを登録 全文検索 重複判定 Redshift api.newspicks.com contents.newspicks.com newspicks.com REST API Java 8 Spring MVC
  44. ELB SQS Elasticsearch RDS (MySQL) ElastiCache (Redis) Dynamo DB DATABASE

    READ / WRITE バックエンドに 非同期タスクを登録 全文検索 重複判定 Redshift api.newspicks.com contents.newspicks.com newspicks.com Java 8 + LOMBOK + SPRING Java 8 Spring MVC
  45. Dynamo DB ElastiCache INTERNET DESKTOP PC MOBILE ROUTE 53 ELB

    EIP INCOMING APP BAT Elasticsearch RDS Redshift CloudWatch SNS SES SQS Java @Logging @Controller @RequestMapping("/news") public interface NewsController { @RequestMapping(value="/{id}/picks", method=GET, produces=ContentType.JSON) PageableCollection<PickViewModel> getPicks( @PathVariable Integer id, @ModelAttribute PickSearchParams params ); } Model と JSON のマッピングがツラい DTO を書くのが面倒くさい
  46. Dynamo DB ElastiCache INTERNET DESKTOP PC MOBILE ROUTE 53 ELB

    EIP INCOMING APP BAT Elasticsearch RDS Redshift CloudWatch SNS SES SQS LOMBOK public class NewsControllerImpl implements NewsController { private final NewsFacade facade; @Inject public NewsControllerImpl(NewsFacade facade) { this.facade = facade; } @Override public PageableCollection<PickViewModel> getPicks(Integer id, PickSearchParams params) { return facade.getPicks(id, params.getSorting(), params.getPage()) .map(pick -> PickViewModel.build(pick)); } }
  47. Dynamo DB ElastiCache INTERNET DESKTOP PC MOBILE ROUTE 53 ELB

    EIP INCOMING APP BAT Elasticsearch RDS Redshift CloudWatch SNS SES SQS LOMBOK @Data public class PickSearchParams { private String sortBy; private String order; private Integer offset; private Integer limit; public Sorting getSorting() { return Sorting.from(sortBy); } public Page getPage() { return Page.builder() .order(Order.from(order)).offset(offset).limit(limit).build(); } } @Builder @Value public class Page { private final Order order; private final Integer offset; private final Integer limit; }
  48. Dynamo DB ElastiCache INTERNET DESKTOP PC MOBILE ROUTE 53 ELB

    EIP INCOMING APP BAT Elasticsearch RDS Redshift CloudWatch SNS SES SQS LOMBOK @Builder @Value public class PickViewModel { public static PickViewModel build(Pick pick) { UserViewModel user = UserViewModel.builder().delegate(pick.getUser()).build(); return PickViewModel.builder().delegate(pick).user(user).build(); } @JsonIgnore @Delegate(types=Picks.class, exclusions=Exclusions.class) private final Pick delegate; private final UserViewModel user; private static interface Exclusions { User getUser(); // + other properties ... } }
  49. Dynamo DB ElastiCache INTERNET DESKTOP PC MOBILE ROUTE 53 ELB

    EIP INCOMING APP BAT Elasticsearch RDS Redshift CloudWatch SNS SES SQS SPRING ぶっちゃけしんどい TOO LARGE TOO COMPLICATED XML HELL SPRING SECURITY プロダクションには JAX-RS + Guice / Dagger がオススメ
  50. ELB SQS Elasticsearch RDS (MySQL) ElastiCache (Redis) Dynamo DB DATABASE

    READ / WRITE バックエンドに 非同期タスクを登録 全文検索 重複判定 Redshift api.newspicks.com contents.newspicks.com newspicks.com Java 8 SQS (Simple Queue Service) 可用性が担保された分散キュー コンシューマはႈ等に実装する必要がある → スループット向上 / バックエンドの分離
  51. Dynamo DB ElastiCache INTERNET DESKTOP PC MOBILE ROUTE 53 ELB

    EIP INCOMING APP BAT Elasticsearch RDS Redshift CloudWatch SNS SES SQS BACKEND BAT SERVER RECOMMEND SERVER etc ..
  52. SQS Elasticsearch RDS (MySQL) ElastiCache (Redis) Dynamo DB CRON Java

    提携サプライヤ/ 独自記事取込 Java ランキング 計算 Java コメント スコア計算 Java 類似記事計算 カテゴリ分類 Java タイムラインの 生成/伝播 Java 検索 インデックス更新 DATABASE READ / WRITE SCHEDULE UPDATE SQS SUBSCRIBE BACKEND SERVICES 他にも様々な サービスがあります Redshift ENQUEUE
  53. SQS Elasticsearch RDS (MySQL) ElastiCache (Redis) Dynamo DB CRON Java

    提携サプライヤ/ 独自記事取込 Java ランキング 計算 Java コメント スコア計算 Java 類似記事計算 カテゴリ分類 Java タイムラインの 生成/伝播 Java 検索 インデックス更新 DATABASE READ / WRITE SCHEDULE UPDATE SQS SUBSCRIBE BACKEND SERVICES 他にも様々な サービスがあります Redshift ENQUEUE
  54. Article Java ユーザー毎の タイムライン 取り込んだ記事がタイムラインに反映されるまで Java Java Categorize Queue Propagate

    Queue Feed Service Categorize Service Propagate Service Vowpal Wabbit Sckit Learn etc. Py Dynamo DB ElastiCache (Redis) 機械学習 エンジン
  55. Article Java ユーザー毎の タイムライン 取り込んだ記事がタイムラインに反映されるまで Java Java Categorize Queue Propagate

    Queue Feed Service Categorize Service Propagate Service Vowpal Wabbit Sckit Learn etc. Py Dynamo DB ElastiCache (Redis) 1. Update Feed 機械学習 エンジン
  56. Article Java ユーザー毎の タイムライン 取り込んだ記事がタイムラインに反映されるまで Java Java Categorize Queue Propagate

    Queue Feed Service Categorize Service Propagate Service Vowpal Wabbit Sckit Learn etc. Py Dynamo DB ElastiCache (Redis) 1. Update Feed 2. Poll 機械学習 エンジン
  57. Article Java ユーザー毎の タイムライン 取り込んだ記事がタイムラインに反映されるまで Java Java Categorize Queue Propagate

    Queue Feed Service Categorize Service Propagate Service Vowpal Wabbit Sckit Learn etc. Py Dynamo DB ElastiCache (Redis) 1. Update Feed 2. Poll 3. Save 機械学習 エンジン
  58. Article Java ユーザー毎の タイムライン 取り込んだ記事がタイムラインに反映されるまで Java Java Categorize Queue Propagate

    Queue Feed Service Categorize Service Propagate Service Vowpal Wabbit Sckit Learn etc. Py Dynamo DB ElastiCache (Redis) 1. Update Feed 2. Poll 3. Save 4. Enqueue 機械学習 エンジン
  59. Article Java ユーザー毎の タイムライン 取り込んだ記事がタイムラインに反映されるまで Java Java Categorize Queue Propagate

    Queue Feed Service Categorize Service Propagate Service Vowpal Wabbit Sckit Learn etc. Py Dynamo DB ElastiCache (Redis) 1. Update Feed 2. Poll 3. Save 4. Enqueue 5. Subscribe 機械学習 エンジン
  60. Article Java ユーザー毎の タイムライン 取り込んだ記事がタイムラインに反映されるまで Java Java Categorize Queue Propagate

    Queue Feed Service Categorize Service Propagate Service Vowpal Wabbit Sckit Learn etc. Py Dynamo DB ElastiCache (Redis) 1. Update Feed 2. Poll 3. Save 4. Enqueue 5. Subscribe 6. Calculate Score 機械学習 エンジン
  61. Article Java ユーザー毎の タイムライン 取り込んだ記事がタイムラインに反映されるまで Java Java Categorize Queue Propagate

    Queue Feed Service Categorize Service Propagate Service Vowpal Wabbit Sckit Learn etc. Py Dynamo DB ElastiCache (Redis) 7. Fetch Data 1. Update Feed 2. Poll 3. Save 4. Enqueue 5. Subscribe 6. Calculate Score 機械学習 エンジン
  62. Article Java ユーザー毎の タイムライン 取り込んだ記事がタイムラインに反映されるまで Java Java Categorize Queue Propagate

    Queue Feed Service Categorize Service Propagate Service Vowpal Wabbit Sckit Learn etc. Py Dynamo DB ElastiCache (Redis) 7. Fetch Data 1. Update Feed 2. Poll 3. Save 4. Enqueue 5. Subscribe 6. Calculate Score 8. Return Score 機械学習 エンジン
  63. Article Java ユーザー毎の タイムライン 取り込んだ記事がタイムラインに反映されるまで Java Java Categorize Queue Propagate

    Queue Feed Service Categorize Service Propagate Service Vowpal Wabbit Sckit Learn etc. Py Dynamo DB ElastiCache (Redis) 7. Fetch Data 1. Update Feed 2. Poll 3. Save 4. Enqueue 5. Subscribe 6. Calculate Score 8. Return Score 9. Save 機械学習 エンジン
  64. Article Java ユーザー毎の タイムライン 取り込んだ記事がタイムラインに反映されるまで Java Java Categorize Queue Propagate

    Queue Feed Service Categorize Service Propagate Service Vowpal Wabbit Sckit Learn etc. Py Dynamo DB ElastiCache (Redis) 7. Fetch Data 1. Update Feed 2. Poll 3. Save 4. Enqueue 5. Subscribe 6. Calculate Score 8. Return Score 9. Save 10. Enqueue 機械学習 エンジン
  65. Article Java ユーザー毎の タイムライン 取り込んだ記事がタイムラインに反映されるまで Java Java Categorize Queue Propagate

    Queue Feed Service Categorize Service Propagate Service Vowpal Wabbit Sckit Learn etc. Py Dynamo DB ElastiCache (Redis) 7. Fetch Data 1. Update Feed 2. Poll 3. Save 4. Enqueue 5. Subscribe 6. Calculate Score 8. Return Score 9. Save 10. Enqueue 11. Subscribe 機械学習 エンジン
  66. Article Java ユーザー毎の タイムライン 取り込んだ記事がタイムラインに反映されるまで Java Java Categorize Queue Propagate

    Queue Feed Service Categorize Service Propagate Service Vowpal Wabbit Sckit Learn etc. Py Dynamo DB ElastiCache (Redis) 7. Fetch Data 1. Update Feed 2. Poll 3. Save 4. Enqueue 5. Subscribe 6. Calculate Score 8. Return Score 9. Save 10. Enqueue 11. Subscribe 12. Update 機械学習 エンジン
  67. AWS 柔軟なスケール調整とインフラ運用コストの低減 Java Java 8 + LOMBOK で堅く軽く開発

  68. AGENDA 1. UZABASE / NewsPicks について 2. NewsPicks の裏側 3.

    急成長を支える技術と舞台裏
  69. そろそろ疲れてきたと思うので 軽めのテイストにします

  70. Redis 編

  71. 入社 1 ヶ月が経ったころ ...

  72. 入社 1 ヶ月が経ったころ ... (” ՞ਊ ՞)” 「Redis が危ないです」 (”

    ՞ਊ ՞)” 「最近はバックアップに失敗することも多いし …」 (” ՞ਊ ՞)” 「深夜アラートも多発しています」
  73. 入社 1 ヶ月が経ったころ ... (” ՞ਊ ՞)” 「Redis が危ないです」 (”

    ՞ਊ ՞)” 「最近はバックアップに失敗することも多いし …」 (” ՞ਊ ՞)” 「深夜アラートも多発しています」 ٩( 'ω' )و 「えっ ……」
  74. 入社 1 ヶ月が経ったころ ... (” ՞ਊ ՞)” 「Redis が危ないです」 (”

    ՞ਊ ՞)” 「最近はバックアップに失敗することも多いし …」 (” ՞ਊ ՞)” 「深夜アラートも多発しています」 ٩( 'ω' )و 「えっ ……」 (” ՞ਊ ՞)” 「このままではタイムラインが動かなくなってしまいます」 (” ՞ਊ ՞)” 「そろそろ Redis をスケールアウトしましょう」
  75. 入社 1 ヶ月が経ったころ ... (” ՞ਊ ՞)” 「Redis が危ないです」 (”

    ՞ਊ ՞)” 「最近はバックアップに失敗することも多いし …」 (” ՞ਊ ՞)” 「深夜アラートも多発しています」 ٩( 'ω' )و 「えっ ……」 (” ՞ਊ ՞)” 「このままではタイムラインが動かなくなってしまいます」 (” ՞ਊ ՞)” 「そろそろ Redis をスケールアウトしましょう」 ٩( 'ω' )و (これがスタートアップの現実か ……)
  76. 当時の Redis EC2 上に立てた Redis のインスタンスが 2 台 ・タイムライン用 ・ランキング

    / キャッシュ用 タイムライン用 Redis にアクセスが集中し遅延が発生 ・ピークタイムにリクエストが集中し遅延 ・夜間バッチ処理が遅延してレプリケーションが切れる イベントループモデル ・Redis の性能を超えた要求
  77. 当時の Redis EC2 上に立てた Redis のインスタンスが 2 台 ・タイムライン用 ・ランキング

    / キャッシュ用 タイムライン用 Redis にアクセスが集中し遅延が発生 ・ピークタイムにリクエストが集中し遅延 ・夜間バッチ処理が遅延してレプリケーションが切れる イベントループモデル ・Redis の性能を超えた要求 ElastiCache に移行
  78. 当時の Redis EC2 上に立てた Redis のインスタンスが 2 台 ・タイムライン用 ・ランキング

    / キャッシュ用 タイムライン用 Redis にアクセスが集中し遅延が発生 ・ピークタイムにリクエストが集中し遅延 ・夜間バッチ処理が遅延してレプリケーションが切れる イベントループモデル ・Redis の性能を超えた要求 ElastiCache に移行 ユーザー パーティショニング (垂直分散)
  79. 当時の Redis EC2 上に立てた Redis のインスタンスが 2 台 ・タイムライン用 ・ランキング

    / キャッシュ用 タイムライン用 Redis にアクセスが集中し遅延が発生 ・ピークタイムにリクエストが集中し遅延 ・夜間バッチ処理が遅延してレプリケーションが切れる イベントループモデル ・Redis の性能を超えた要求 ElastiCache に移行 ユーザー パーティショニング (垂直分散) BAT サーバーも スケールアウト
  80. BEFORE ELB APP BAT Redis (slave) Redis (master) replication

  81. BEFORE ELB APP BAT Redis (slave) Redis (master) 1台で 全ユーザーの

    タイムラインを更新 replication
  82. BEFORE ELB APP BAT Redis (slave) Redis (master) 1台で 全ユーザーの

    タイムラインを更新 replication 自前で レプリケーション & バックアップ
  83. BEFORE ELB APP BAT Redis (slave) Redis (master) 1台で 全ユーザーの

    タイムラインを更新 replication 自前で レプリケーション & バックアップ BAT サーバーは 1 台で処理
  84. BEFORE ELB APP BAT Redis (slave) Redis (master) 1台で 全ユーザーの

    タイムラインを更新 replication 自前で レプリケーション & バックアップ AFTER ELB APP BAT slave master replication ElastiCache (Redis) slave master replication ElastiCache (Redis) slave master replication ElastiCache (Redis) USER ID 1-10 万 USER ID 10 -20 万 USER ID 20 -30 万 BAT サーバーは 1 台で処理
  85. BEFORE ELB APP BAT Redis (slave) Redis (master) 1台で 全ユーザーの

    タイムラインを更新 replication 自前で レプリケーション & バックアップ AFTER ELB APP BAT slave master replication ElastiCache (Redis) slave master replication ElastiCache (Redis) slave master replication ElastiCache (Redis) USER ID 1-10 万 USER ID 10 -20 万 USER ID 20 -30 万 BAT サーバーは 1 台で処理 ElastiCache に移行 ユーザー ID で パーティショニング
  86. BEFORE ELB APP BAT Redis (slave) Redis (master) 1台で 全ユーザーの

    タイムラインを更新 replication 自前で レプリケーション & バックアップ Redis 分散にあわせて BAT サーバーも 複数台に増やして 高速化を図る AFTER ELB APP BAT slave master replication ElastiCache (Redis) slave master replication ElastiCache (Redis) slave master replication ElastiCache (Redis) USER ID 1-10 万 USER ID 10 -20 万 USER ID 20 -30 万 BAT サーバーは 1 台で処理 ElastiCache に移行 ユーザー ID で パーティショニング
  87. BEFORE ELB APP BAT Redis (slave) Redis (master) 1台で 全ユーザーの

    タイムラインを更新 replication 自前で レプリケーション & バックアップ Redis 分散にあわせて BAT サーバーも 複数台に増やして 高速化を図る AFTER ELB APP BAT slave master replication ElastiCache (Redis) slave master replication ElastiCache (Redis) slave master replication ElastiCache (Redis) USER ID 1-10 万 USER ID 10 -20 万 USER ID 20 -30 万 BAT サーバーは 1 台で処理 ElastiCache に移行 ユーザー ID で パーティショニング public class TimelineRepositoryImpl implements TimelineRepository { private final TimelineRedisTemplateSelector timelineRedisSelector; @Override public void addPickToTimeline(Pick pick) { redis(pick.getUserId()).execute(new AddPickTxCallback(pick) { ... }); } private TimelineRedisTemplate redis(Integer userId) { return timelineRedisSelector.select(userId); } }
  88. 垂直分散後

  89. 垂直分散後 (” ՞ਊ ՞)” 「これで一安心ですね」 (” ՞ਊ ՞)” 「しばらくはゆっくりとした夜が過ごせるでしょう」

  90. 垂直分散後 (” ՞ਊ ՞)” 「これで一安心ですね」 (” ՞ਊ ՞)” 「しばらくはゆっくりとした夜が過ごせるでしょう」 一週間後

  91. 垂直分散後 (” ՞ਊ ՞)” 「これで一安心ですね」 (” ՞ਊ ՞)” 「しばらくはゆっくりとした夜が過ごせるでしょう」 一週間後

    (” ՞ਊ ՞)” 「垂直分散では負荷が思ったほど下がりませんね」 (” ՞ਊ ՞)” 「人気ユーザーが一部のパーティションに集中しているようです」 (” ՞ਊ ՞)” 「やはりハッシュで分散しましょう」 (” ՞ਊ ՞)” 「リバランスは運用でカバーっしょ!」
  92. 垂直分散後 (” ՞ਊ ՞)” 「これで一安心ですね」 (” ՞ਊ ՞)” 「しばらくはゆっくりとした夜が過ごせるでしょう」 ٩(

    'ω' )و (思った通りの結果になったな ……) 一週間後 (” ՞ਊ ՞)” 「垂直分散では負荷が思ったほど下がりませんね」 (” ՞ਊ ՞)” 「人気ユーザーが一部のパーティションに集中しているようです」 (” ՞ਊ ՞)” 「やはりハッシュで分散しましょう」 (” ՞ਊ ՞)” 「リバランスは運用でカバーっしょ!」
  93. BEFORE ELB APP BAT Redis (slave) Redis (master) 1台で 全ユーザーの

    タイムラインを更新 replication 自前で レプリケーション & バックアップ Redis 分散にあわせて BAT サーバーも 複数台に増やして 高速化を図る AFTER ELB APP BAT slave master replication ElastiCache (Redis) slave master replication ElastiCache (Redis) slave master replication ElastiCache (Redis) USER ID 1-10 万 USER ID 10 -20 万 USER ID 20 -30 万 BAT サーバーは 1 台で処理 ElastiCache に移行 ユーザー ID で パーティショニング public class TimelineRepositoryImpl implements TimelineRepository { private final TimelineRedisTemplateSelector timelineRedisSelector; @Override public void addPickToTimeline(Pick pick) { redis(pick.getUserId()).execute(new AddPickTxCallback(pick) { ... }); } private TimelineRedisTemplate redis(Integer userId) { return timelineRedisSelector.select(userId); } }
  94. ハッシュ分散後

  95. ハッシュ分散後 (” ՞ਊ ՞)” 「これで一安心ですね」 (” ՞ਊ ՞)” 「しばらくはゆっくりとした夜が過ごせるでしょう」

  96. ハッシュ分散後 (” ՞ਊ ՞)” 「これで一安心ですね」 (” ՞ਊ ՞)” 「しばらくはゆっくりとした夜が過ごせるでしょう」 二週間後

  97. ハッシュ分散後 (” ՞ਊ ՞)” 「これで一安心ですね」 (” ՞ਊ ՞)” 「しばらくはゆっくりとした夜が過ごせるでしょう」 二週間後

    (” ՞ਊ ՞)” 「コメントの表示が遅いですね」 (” ՞ਊ ՞)” 「どうやらキャッシュ用の Redis がやられたようです」 (” ՞ਊ ՞)” 「奴は四天王でも最弱 ……」 (” ՞ਊ ՞)” 「30 万ユーザー程度に負けるとは KVS の面汚しよ」
  98. ハッシュ分散後 (” ՞ਊ ՞)” 「これで一安心ですね」 (” ՞ਊ ՞)” 「しばらくはゆっくりとした夜が過ごせるでしょう」 二週間後

    (” ՞ਊ ՞)” 「コメントの表示が遅いですね」 (” ՞ਊ ՞)” 「どうやらキャッシュ用の Redis がやられたようです」 (” ՞ਊ ՞)” 「奴は四天王でも最弱 ……」 (” ՞ਊ ՞)” 「30 万ユーザー程度に負けるとは KVS の面汚しよ」 ٩( 'ω' )و (なに言ってるんだこの人 ……) ٩( 'ω' )و 「READ / WRITE 分散ですね分かります」
  99. BEFORE ELB APP BAT Redis (slave) Redis (master) replication 自前で

    レプリケーション & バックアップ Read / Write Read / Write
  100. BEFORE ELB APP BAT Redis (slave) Redis (master) 1台で全ての 書き込み/読み込みを

    処理 replication 自前で レプリケーション & バックアップ Read / Write Read / Write
  101. BEFORE ELB APP BAT Redis (slave) Redis (master) 1台で全ての 書き込み/読み込みを

    処理 replication 自前で レプリケーション & バックアップ Read / Write Read / Write AFTER ELB APP BAT slave master ElastiCache (Redis) Write slave slave replication replication Read Read
  102. BEFORE ELB APP BAT Redis (slave) Redis (master) 1台で全ての 書き込み/読み込みを

    処理 replication 自前で レプリケーション & バックアップ Read / Write Read / Write AFTER ELB APP BAT slave master ElastiCache (Redis) Write slave slave replication replication Read Read 書き込みは マスターに
  103. BEFORE ELB APP BAT Redis (slave) Redis (master) 1台で全ての 書き込み/読み込みを

    処理 replication 自前で レプリケーション & バックアップ Read / Write Read / Write AFTER ELB APP BAT slave master ElastiCache (Redis) Write slave slave replication replication Read Read 読み込みは リードレプリカから (スティッキー) 書き込みは マスターに
  104. BEFORE ELB APP BAT Redis (slave) Redis (master) 1台で全ての 書き込み/読み込みを

    処理 replication 自前で レプリケーション & バックアップ Read / Write Read / Write AFTER ELB APP BAT slave master ElastiCache (Redis) Write slave slave replication replication Read Read 読み込みは リードレプリカから (スティッキー) 書き込みは マスターに リードレプリカが 落ちた場合は フェイルオーバー
  105. BEFORE ELB APP BAT Redis (slave) Redis (master) 1台で全ての 書き込み/読み込みを

    処理 replication 自前で レプリケーション & バックアップ Read / Write Read / Write AFTER ELB APP BAT slave master ElastiCache (Redis) Write slave slave replication replication Read Read 読み込みは リードレプリカから (スティッキー) 書き込みは マスターに リードレプリカが 落ちた場合は フェイルオーバー public class PickRepositoryImpl implements PickRepository { private final ReadWriteRedisTemplateSelector redisSelector; @Override public boolean isLiked(Pick pick, Integer user) { return new LikeOps(redisSelector.read(), user).isLiked(pick); } @Override public boolean like(Pick pick, Integer user) { return new LikeOps(redisSelector.write(), user).like(pick); } }
  106. SEO 編

  107. 入社 2 ヶ月が経ったころ ...

  108. 入社 2 ヶ月が経ったころ ... (” ՞ਊ ՞)” 「SEO がヤバいです」 (”

    ՞ਊ ՞)” 「NewsPicks は Google にインデックスされていません」 (” ՞ਊ ՞)” 「新時代の経済メディアとしては由々しき事態です」
  109. 入社 2 ヶ月が経ったころ ... (” ՞ਊ ՞)” 「SEO がヤバいです」 (”

    ՞ਊ ՞)” 「NewsPicks は Google にインデックスされていません」 (” ՞ਊ ՞)” 「新時代の経済メディアとしては由々しき事態です」 ٩( 'ω' )و 「えっ ……」
  110. 入社 2 ヶ月が経ったころ ... (” ՞ਊ ՞)” 「SEO がヤバいです」 (”

    ՞ਊ ՞)” 「NewsPicks は Google にインデックスされていません」 (” ՞ਊ ՞)” 「新時代の経済メディアとしては由々しき事態です」 ٩( 'ω' )و 「えっ ……」 (” ՞ਊ ՞)” 「編集部が頑張っても検索から流入しないのは困りものです」 (” ՞ਊ ՞)” 「そろそろSEO 対策をするときが来たようです」
  111. 入社 2 ヶ月が経ったころ ... (” ՞ਊ ՞)” 「SEO がヤバいです」 (”

    ՞ਊ ՞)” 「NewsPicks は Google にインデックスされていません」 (” ՞ਊ ՞)” 「新時代の経済メディアとしては由々しき事態です」 ٩( 'ω' )و 「えっ ……」 (” ՞ਊ ՞)” 「編集部が頑張っても検索から流入しないのは困りものです」 (” ՞ਊ ՞)” 「そろそろSEO 対策をするときが来たようです」 ٩( 'ω' )و (スタートアップは大変だな ……) ٩( 'ω' )و (Phantom でちょちょいと描画すれば良かろう ……)
  112. 当時の SEO 状況 夏に Web 版をリリース ・Angular で開発された SPA ・時間とリソースの関係上

    SEO 対策まったく無し 秋に編集部が発足 ・独自記事が徐々に拡散されるように ・ソーシャルからの流入は増えてきたが検索から全く流入しない
  113. 翌日

  114. 翌日 ٩( 'ω' )و 「真面目に作る時間も勿体無いので ……」 ٩( 'ω' )و 「Phantom

    でスナップショットを返すようにしました」 ٩( 'ω' )و 「ぽちっとリリースしたけど Phantom の暴走が心配です」
  115. 翌日 (” ՞ਊ ՞)” 「しばらく様子を見てみましょう」 (” ՞ਊ ՞)” 「念のため node

    を自動再起動するようにしておきましょう」 ٩( 'ω' )و 「真面目に作る時間も勿体無いので ……」 ٩( 'ω' )و 「Phantom でスナップショットを返すようにしました」 ٩( 'ω' )و 「ぽちっとリリースしたけど Phantom の暴走が心配です」
  116. 翌日 (” ՞ਊ ՞)” 「しばらく様子を見てみましょう」 (” ՞ਊ ՞)” 「念のため node

    を自動再起動するようにしておきましょう」 ٩( 'ω' )و 「はい」 ٩( 'ω' )و 「取り敢えず forever で動かしときました」 ٩( 'ω' )و 「真面目に作る時間も勿体無いので ……」 ٩( 'ω' )و 「Phantom でスナップショットを返すようにしました」 ٩( 'ω' )و 「ぽちっとリリースしたけど Phantom の暴走が心配です」
  117. 3 日後

  118. 3 日後 (” ՞ਊ ՞)” 「インデックスに登録されるようになってきましたね」 (” ՞ਊ ՞)” 「遂に

    NewsPicks にも検索から流入する時代が ……」 (” ՞ਊ ՞)” 「しかしクロールエラーが頻発していますね」
  119. 3 日後 (” ՞ਊ ՞)” 「インデックスに登録されるようになってきましたね」 (” ՞ਊ ՞)” 「遂に

    NewsPicks にも検索から流入する時代が ……」 (” ՞ਊ ՞)” 「しかしクロールエラーが頻発していますね」 ٩( 'ω' )و 「やはり Phantom が暴走しまくってます ……」
  120. 3 日後 (” ՞ਊ ՞)” 「インデックスに登録されるようになってきましたね」 (” ՞ਊ ՞)” 「遂に

    NewsPicks にも検索から流入する時代が ……」 (” ՞ਊ ՞)” 「しかしクロールエラーが頻発していますね」 ٩( 'ω' )و 「やはり Phantom が暴走しまくってます ……」
  121. 3 日後 (” ՞ਊ ՞)” 「インデックスに登録されるようになってきましたね」 (” ՞ਊ ՞)” 「遂に

    NewsPicks にも検索から流入する時代が ……」 (” ՞ਊ ՞)” 「しかしクロールエラーが頻発していますね」 ٩( 'ω' )و 「やはり Phantom が暴走しまくってます ……」
  122. 3 日後 (” ՞ਊ ՞)” 「インデックスに登録されるようになってきましたね」 (” ՞ਊ ՞)” 「遂に

    NewsPicks にも検索から流入する時代が ……」 (” ՞ਊ ՞)” 「しかしクロールエラーが頻発していますね」 ٩( 'ω' )و 「やはり Phantom が暴走しまくってます ……」 (” ՞ਊ ՞)” 「さっさと直して下さい」 (” ՞ਊ ՞)” 「明日からクロールエラーはダメよ〜ダメダメ」
  123. 3 日後 (” ՞ਊ ՞)” 「インデックスに登録されるようになってきましたね」 (” ՞ਊ ՞)” 「遂に

    NewsPicks にも検索から流入する時代が ……」 (” ՞ਊ ՞)” 「しかしクロールエラーが頻発していますね」 ٩( 'ω' )و 「やはり Phantom が暴走しまくってます ……」 (” ՞ਊ ՞)” 「さっさと直して下さい」 (” ՞ਊ ՞)” 「明日からクロールエラーはダメよ〜ダメダメ」 ٩( 'ω' )و (最近この人よくこのフレーズ使うな ……) ٩( 'ω' )و (流行語か何かなのかな ……)
  124. 3 日後 ٩( 'ω' )و 「node-phantom が悪さをしていたみたいです」 ٩( 'ω' )و

    「child_process を使うと良い感じになりました」
  125. 3 日後 ٩( 'ω' )و 「node-phantom が悪さをしていたみたいです」 ٩( 'ω' )و

    「child_process を使うと良い感じになりました」
  126. 3 日後 ٩( 'ω' )و 「node-phantom が悪さをしていたみたいです」 ٩( 'ω' )و

    「child_process を使うと良い感じになりました」
  127. 3 日後 ٩( 'ω' )و 「node-phantom が悪さをしていたみたいです」 ٩( 'ω' )و

    「child_process を使うと良い感じになりました」 (” ՞ਊ ՞)” 「良いじゃないですか」 (” ՞ਊ ՞)” 「しばらくコレで運用していきましょう」 (” ՞ਊ ՞)” 「これは SEO 対策の第一歩ですからね」
  128. 3 日後 ٩( 'ω' )و 「node-phantom が悪さをしていたみたいです」 ٩( 'ω' )و

    「child_process を使うと良い感じになりました」 (” ՞ਊ ՞)” 「良いじゃないですか」 (” ՞ਊ ՞)” 「しばらくコレで運用していきましょう」 (” ՞ਊ ՞)” 「これは SEO 対策の第一歩ですからね」 ٩( 'ω' )و 「はい」 ٩( 'ω' )و (省コストで対応出来て良かった ……)
  129. 急成長の代償との闘争編

  130. 入社 3 ヶ月が経ったころ ...

  131. ٩( 'ω' )و 「この API って何ですか?」 ٩( 'ω' )و 「JSON

    に要らないプロパティが大量にあって重いんですけど」 ٩( 'ω' )و 「noinclude っていう変なパラメータがあるんですけど」 入社 3 ヶ月が経ったころ ...
  132. (” ՞ਊ ՞)” 「ソフトウェアに歴史アリです」 (” ՞ਊ ՞)” 「急成長の代償に失われたものがあるのです」 ٩( 'ω'

    )و 「この API って何ですか?」 ٩( 'ω' )و 「JSON に要らないプロパティが大量にあって重いんですけど」 ٩( 'ω' )و 「noinclude っていう変なパラメータがあるんですけど」 入社 3 ヶ月が経ったころ ...
  133. (” ՞ਊ ՞)” 「ソフトウェアに歴史アリです」 (” ՞ਊ ՞)” 「急成長の代償に失われたものがあるのです」 ٩( 'ω'

    )و 「わかります」 ٩( 'ω' )و 「リファクタして良いですか?」 ٩( 'ω' )و 「この API って何ですか?」 ٩( 'ω' )و 「JSON に要らないプロパティが大量にあって重いんですけど」 ٩( 'ω' )و 「noinclude っていう変なパラメータがあるんですけど」 入社 3 ヶ月が経ったころ ...
  134. 当時のコードの状況 API の後方互換性を維持したままひたすら拡張 ・既に使われていない大量のプロパティ ・同じモデルが複数の API で使い回されてひたすら拡張されていた ・最早どこかを弄ったらどこかが壊れる状態 レイヤリングが殆どされていない ・業務レイヤにまで

    Web レイヤの実装が登場 ・単体テストなど夢のまた夢 急成長の代償なので一概に悪いものでは無い ・スタートアップは生きるか死ぬか
  135. とりあえず

  136. ٩( 'ω' )و 「少しずつ依存を切り離して ……」 ٩( 'ω' )و 「業務レイヤは独立させよう」 ٩(

    'ω' )و 「とはいえ時間が無いので少しずつやっていくか ……」 とりあえず
  137. (” ՞ਊ ՞)” 「スタートアップは生きるか死ぬかですからね」 (” ՞ਊ ՞)” 「バランスが大事です」 ٩( 'ω'

    )و 「少しずつ依存を切り離して ……」 ٩( 'ω' )و 「業務レイヤは独立させよう」 ٩( 'ω' )و 「とはいえ時間が無いので少しずつやっていくか ……」 とりあえず
  138. (” ՞ਊ ՞)” 「スタートアップは生きるか死ぬかですからね」 (” ՞ਊ ՞)” 「バランスが大事です」 ٩( 'ω'

    )و 「わかります」 ٩( 'ω' )و 「あ、この実装まるっと消しますね」 ٩( 'ω' )و 「少しずつ依存を切り離して ……」 ٩( 'ω' )و 「業務レイヤは独立させよう」 ٩( 'ω' )و 「とはいえ時間が無いので少しずつやっていくか ……」 とりあえず
  139. もくもく

  140. ٩( 'ω' )و 「/api/v2/ なんて幻想だな ……」 ٩( 'ω' )و 「C

    向けアプリなんてすぐに要件が変わってくるし」 ٩( 'ω' )و 「エンドポイント毎にバージョンをサクッと切り替えたいな」 もくもく
  141. (” ՞ਊ ՞)” 「スタートアップは生きるか死ぬかですからね」 (” ՞ਊ ՞)” 「バランスが大事です」 ٩( 'ω'

    )و 「/api/v2/ なんて幻想だな ……」 ٩( 'ω' )و 「C 向けアプリなんてすぐに要件が変わってくるし」 ٩( 'ω' )و 「エンドポイント毎にバージョンをサクッと切り替えたいな」 もくもく
  142. (” ՞ਊ ՞)” 「スタートアップは生きるか死ぬかですからね」 (” ՞ਊ ՞)” 「バランスが大事です」 ٩( 'ω'

    )و 「わかります」 ٩( 'ω' )و (もう REST じゃなくて RPC で良いよな ……) ٩( 'ω' )و 「/api/v2/ なんて幻想だな ……」 ٩( 'ω' )و 「C 向けアプリなんてすぐに要件が変わってくるし」 ٩( 'ω' )و 「エンドポイント毎にバージョンをサクッと切り替えたいな」 もくもく
  143. (” ՞ਊ ՞)” 「スタートアップは生きるか死ぬかですからね」 (” ՞ਊ ՞)” 「バランスが大事です」 ٩( 'ω'

    )و 「わかります」 ٩( 'ω' )و (もう REST じゃなくて RPC で良いよな ……) ٩( 'ω' )و 「/api/v2/ なんて幻想だな ……」 ٩( 'ω' )و 「C 向けアプリなんてすぐに要件が変わってくるし」 ٩( 'ω' )و 「エンドポイント毎にバージョンをサクッと切り替えたいな」 もくもく @Controller public class NewsController extends ControllerBase { @RequestMapping(value = "/{id}/picks", method = GET, headers = Headers.API_VERSION_2) @ResponseBody public PageableCollection<PickViewModelV2> getPicks( @PathVariable Long id, @ModelAttribute PickSearchParams params) { return getPicks(id, params).map(PickViewModelV2.mapper()); } @RequestMapping(value = "/{id}/picks", method = GET, headers = Headers.API_VERSION_3) @ResponseBody public PageableCollection<PickViewModelV3> getPicks( @PathVariable Long id, @ModelAttribute PickSearchParams params) { return getPicks(id, params).map(PickViewModelV3.mapper()); } } "CASUAL" VERSION UP
  144. 年末 ٩( 'ω' )و 「来年は少しずつリファクタしていきたいな」 ٩( 'ω' )و 「まずは大量に出ているエラーメールを撲滅するところから始めよう」 ٩(

    'ω' )و 「来年はコードベースをまるっと変えるのを目標にしよう」 ٩( 'ω' )و 「年明けから頑張るぞい」
  145. グロース編

  146. 入社 4 ヶ月が経ったころ ...

  147. 入社 4 ヶ月が経ったころ ... (” ՞ਊ ՞)” 「DAU が伸び悩んでいます」 (”

    ՞ਊ ՞)” 「そろそろ感覚で改善していては駄目なようです」 (” ՞ਊ ՞)” 「チームをつくってグロースハックをして下さい」
  148. 入社 4 ヶ月が経ったころ ... (” ՞ਊ ՞)” 「DAU が伸び悩んでいます」 (”

    ՞ਊ ՞)” 「そろそろ感覚で改善していては駄目なようです」 (” ՞ਊ ՞)” 「チームをつくってグロースハックをして下さい」 ٩( 'ω' )و (今年の目標が早くも崩れた ……)
  149. 入社 4 ヶ月が経ったころ ... (” ՞ਊ ՞)” 「DAU が伸び悩んでいます」 (”

    ՞ਊ ՞)” 「そろそろ感覚で改善していては駄目なようです」 (” ՞ਊ ՞)” 「チームをつくってグロースハックをして下さい」 ٩( 'ω' )و (今年の目標が早くも崩れた ……) (” ՞ਊ ՞)” 「事業計画上の数値はこうです」 (” ՞ਊ ՞)” 「今日と明日は分析をして来週から数値をこれだけ上げましょう」
  150. 入社 4 ヶ月が経ったころ ... (” ՞ਊ ՞)” 「DAU が伸び悩んでいます」 (”

    ՞ਊ ՞)” 「そろそろ感覚で改善していては駄目なようです」 (” ՞ਊ ՞)” 「チームをつくってグロースハックをして下さい」 ٩( 'ω' )و (今年の目標が早くも崩れた ……) (” ՞ਊ ՞)” 「事業計画上の数値はこうです」 (” ՞ਊ ՞)” 「今日と明日は分析をして来週から数値をこれだけ上げましょう」 ٩( 'ω' )و (スピード感がヤバい) ٩( 'ω' )و (そもそもログは残ってるんだっけ ……?)
  151. REDSHIFT とりあえずトラッキングログを整備 ユーザー行動の分析 KPI ダッシュボードの作成

  152. KPI DASHBOARD

  153. KPI DASHBOARD

  154. KPI DASHBOARD

  155. KPI DASHBOARD SPRING BOOT + DOMA 2 + ANGULAR JS

    + D3
  156. KPI DASHBOARD SPRING BOOT + DOMA 2 + ANGULAR JS

    + D3 管理画面をサクっと作るには最適
  157. KPI DASHBOARD SPRING BOOT + DOMA 2 + ANGULAR JS

    + D3 管理画面のようなサクっと作るアプリには オーバースペック気味 …… 分析用のサーバーが Redshift に 格納したデータを取得
  158. KPI DASHBOARD SPRING BOOT + DOMA 2 + ANGULAR JS

    + D3 管理画面のようなサクっと作るアプリには オーバースペック気味 …… 分析用のサーバーが Redshift に 格納したデータを取得 SELECT news.news, count(news.id) "count" FROM news /*%if condition.paidUser != null*/ INNER JOIN users ON news.user_id = users.user_id /*%if condition.paidUser */ AND users.paid_expired >= current_date /*%else*/ AND (users.paid_expired < current_date OR users.paid_expired is null) /*%end*/ /*%end*/ /*%if condition.userType != null*/ INNER JOIN users ON news.feed = users.user_id /*%if condition.userType == "original" */ AND users.type = 'ORIGINAL_CONTENTS' /*%else*/ AND users.type != 'ORIGINAL_CONTENTS' /*%end*/ /*%end*/ WHERE news.time between /*condition.from*/date'2015-01-07 00:00:00' and /*condition.to*/date'2015-01-15 23:59:59' /*%if condition.newsType != null */ AND news.type = /*condition.newsType*/'body' /*%end*/ /*%if condition.free != null */ AND news.paid /*%if condition.free*/ is null /*%else*/ = true/*%end*/ /*%end*/ /*%if(condition.mobileDevices != null && condition.web)*/ AND 正直しんどい
  159. KPI DASHBOARD SPRING BOOT + DOMA 2 + ANGULAR JS

    + D3 管理画面のようなサクっと作るアプリには オーバースペック気味 …… 分析用のサーバーが Redshift に 格納したデータを取得 SQL2O SIMPLE JDBC QUERY LIBRARY public List<UsersEvent> findOpenNewsEvents(Date from, Date to) { String query = "select * from users_events where time >= :from and time < :to and event = ..."; try (Connection connection = db.open()) { return connection.createQuery(query) .throwOnMappingFailure(false) .addParameter("from", from) .addParameter("to", to) .executeAndFetch(UsersEvent.class); } }
  160. その後

  161. ٩( 'ω' )و 「ダッシュボードが出来ました」 ٩( 'ω' )و 「ユーザー行動も分析しました」 ٩( 'ω'

    )و 「やはり ◦◦ の辺りが怪しいです」 その後
  162. (” ՞ਊ ՞)” 「やっと数値が見られるようになりましたね。喜ばしいことです」 (” ՞ਊ ՞)” 「では早速 KAIZEN しましょう。結果を必ず分析しましょうね」

    ٩( 'ω' )و 「ダッシュボードが出来ました」 ٩( 'ω' )و 「ユーザー行動も分析しました」 ٩( 'ω' )و 「やはり ◦◦ の辺りが怪しいです」 その後
  163. (” ՞ਊ ՞)” 「やっと数値が見られるようになりましたね。喜ばしいことです」 (” ՞ਊ ՞)” 「では早速 KAIZEN しましょう。結果を必ず分析しましょうね」

    ٩( 'ω' )و 「はい」 ٩( 'ω' )و (そういえば A/B テストとか出来るのかな ……) ٩( 'ω' )و 「ダッシュボードが出来ました」 ٩( 'ω' )و 「ユーザー行動も分析しました」 ٩( 'ω' )و 「やはり ◦◦ の辺りが怪しいです」 その後
  164. (” ՞ਊ ՞)” 「やっと数値が見られるようになりましたね。喜ばしいことです」 (” ՞ਊ ՞)” 「では早速 KAIZEN しましょう。結果を必ず分析しましょうね」

    ٩( 'ω' )و 「はい」 ٩( 'ω' )و (そういえば A/B テストとか出来るのかな ……) ٩( 'ω' )و 「ダッシュボードが出来ました」 ٩( 'ω' )و 「ユーザー行動も分析しました」 ٩( 'ω' )و 「やはり ◦◦ の辺りが怪しいです」 その後 当然できなかった
  165. 悲しいのでサクっと改修した

  166. ٩( 'ω' )و 「アプリの文言はサーバーから取得するようにして ……」 ٩( 'ω' )و 「タブ情報もユーザー毎に切り替えられるように ……」

    ٩( 'ω' )و 「ユーザーをセグメント化して一部ユーザーだけにリリースしたいな」 悲しいのでサクっと改修した
  167. (” ՞ਊ ՞)” 「最小のコストで最大の成果をデスヨ」 (” ՞ਊ ՞)” (開発なんてしてんじゃねーよ) ٩( 'ω'

    )و 「アプリの文言はサーバーから取得するようにして ……」 ٩( 'ω' )و 「タブ情報もユーザー毎に切り替えられるように ……」 ٩( 'ω' )و 「ユーザーをセグメント化して一部ユーザーだけにリリースしたいな」 悲しいのでサクっと改修した
  168. (” ՞ਊ ՞)” 「最小のコストで最大の成果をデスヨ」 (” ՞ਊ ՞)” (開発なんてしてんじゃねーよ) ٩( 'ω'

    )و 「アプリの文言はサーバーから取得するようにして ……」 ٩( 'ω' )و 「タブ情報もユーザー毎に切り替えられるように ……」 ٩( 'ω' )و 「ユーザーをセグメント化して一部ユーザーだけにリリースしたいな」 悲しいのでサクっと改修した ٩( 'ω' )و 「わかります」 ٩( 'ω' )و 「UPDATE features SET roles = 'BETA-TESTERS' WHERE id = 'AWESOME_FEATURE'」 ٩( 'ω' )و 「カチャカチャ・・・ッターン!」 ٩( 'ω' )و (ドヤァ・・・)
  169. 社内メンバー限定公開機能も作った VERY GOOD GOOD BAD RECOMMEND ENGINE

  170. 社内メンバー限定公開機能も作った VERY GOOD GOOD BAD RECOMMEND ENGINE ふむふむ (学習中)

  171. その後

  172. ٩( 'ω' )و 「Facebook ページが息をしていないようです」 ٩( 'ω' )و 「メルマガを配信するお金が無いです」 ٩(

    'ω' )و 「プッシュ通知が〜」 その後
  173. (” ՞ਊ ՞)” 「最小のコストで最大の成果をデスヨ」 (” ՞ਊ ՞)” 「地道な努力が必要デスヨ」 ٩( 'ω'

    )و 「Facebook ページが息をしていないようです」 ٩( 'ω' )و 「メルマガを配信するお金が無いです」 ٩( 'ω' )و 「プッシュ通知が〜」 その後
  174. (” ՞ਊ ՞)” 「最小のコストで最大の成果をデスヨ」 (” ՞ਊ ՞)” 「地道な努力が必要デスヨ」 ٩( 'ω'

    )و 「Facebook ページが息をしていないようです」 ٩( 'ω' )و 「メルマガを配信するお金が無いです」 ٩( 'ω' )و 「プッシュ通知が〜」 その後 ٩( 'ω' )و 「わかります」 ٩( 'ω' )و 「取り敢えずメール配信サービス契約しますね」 ٩( 'ω' )و 「リテンション・・・ダイジ・・・AARRR・・・」
  175. (” ՞ਊ ՞)” 「最小のコストで最大の成果をデスヨ」 (” ՞ਊ ՞)” 「地道な努力が必要デスヨ」 ٩( 'ω'

    )و 「Facebook ページが息をしていないようです」 ٩( 'ω' )و 「メルマガを配信するお金が無いです」 ٩( 'ω' )و 「プッシュ通知が〜」 その後 ٩( 'ω' )و 「わかります」 ٩( 'ω' )و 「取り敢えずメール配信サービス契約しますね」 ٩( 'ω' )و 「リテンション・・・ダイジ・・・AARRR・・・」 闘いの日々は続く
  176. NewsPicks Angular やめるってよ編

  177. 入社 5 ヶ月が経ったころ ...

  178. 入社 5 ヶ月が経ったころ ... (” ՞ਊ ՞)” 「ウェブ版がヤバいです」 (” ՞ਊ

    ՞)” 「SEO の効果もイマイチだし」 (” ՞ਊ ՞)” 「モバイル版は初期表示が遅すぎます」
  179. 入社 5 ヶ月が経ったころ ... (” ՞ਊ ՞)” 「ウェブ版がヤバいです」 (” ՞ਊ

    ՞)” 「SEO の効果もイマイチだし」 (” ՞ਊ ՞)” 「モバイル版は初期表示が遅すぎます」 ٩( 'ω' )و (Angular 使ってるからな・・・)
  180. 入社 5 ヶ月が経ったころ ... (” ՞ਊ ՞)” 「ウェブ版がヤバいです」 (” ՞ਊ

    ՞)” 「SEO の効果もイマイチだし」 (” ՞ਊ ՞)” 「モバイル版は初期表示が遅すぎます」 ٩( 'ω' )و (Angular 使ってるからな・・・) (” ՞ਊ ՞)” 「デザインリニューアルもしたいですが」 (” ՞ਊ ՞)” 「取り敢えず Angular 止めようぜ」
  181. 入社 5 ヶ月が経ったころ ... (” ՞ਊ ՞)” 「ウェブ版がヤバいです」 (” ՞ਊ

    ՞)” 「SEO の効果もイマイチだし」 (” ՞ਊ ՞)” 「モバイル版は初期表示が遅すぎます」 ٩( 'ω' )و (Angular 使ってるからな・・・) (” ՞ਊ ՞)” 「デザインリニューアルもしたいですが」 (” ՞ਊ ՞)” 「取り敢えず Angular 止めようぜ」 ٩( 'ω' )و 「わかります」 ٩( 'ω' )و 「Server Side React ですね」
  182. 入社 5 ヶ月が経ったころ ... (” ՞ਊ ՞)” 「技術者のエゴはダメよ〜ダメダメ」 (” ՞ਊ

    ՞)” 「テスト含めて 3 週間で作り直し頼む」 (” ՞ਊ ՞)” 「エンジニアは 1.5 人ぐらいで頼む」
  183. 入社 5 ヶ月が経ったころ ... (” ՞ਊ ՞)” 「技術者のエゴはダメよ〜ダメダメ」 (” ՞ਊ

    ՞)” 「テスト含めて 3 週間で作り直し頼む」 (” ՞ਊ ՞)” 「エンジニアは 1.5 人ぐらいで頼む」 ٩( 'ω' )و (スピード感がヤバい)
  184. 入社 5 ヶ月が経ったころ ... (” ՞ਊ ՞)” 「技術者のエゴはダメよ〜ダメダメ」 (” ՞ਊ

    ՞)” 「テスト含めて 3 週間で作り直し頼む」 (” ՞ਊ ՞)” 「エンジニアは 1.5 人ぐらいで頼む」 ٩( 'ω' )و (スピード感がヤバい) (” ՞ਊ ՞)” 「アプリのコピーではなくメディアサイトに作り直したいので」 (” ՞ਊ ՞)” 「ちょちょっと直してよ、あ、IE 7 対応でおなしゃす!」
  185. 入社 5 ヶ月が経ったころ ... (” ՞ਊ ՞)” 「技術者のエゴはダメよ〜ダメダメ」 (” ՞ਊ

    ՞)” 「テスト含めて 3 週間で作り直し頼む」 (” ՞ਊ ՞)” 「エンジニアは 1.5 人ぐらいで頼む」 ٩( 'ω' )و (スピード感がヤバい) (” ՞ਊ ՞)” 「アプリのコピーではなくメディアサイトに作り直したいので」 (” ՞ਊ ՞)” 「ちょちょっと直してよ、あ、IE 7 対応でおなしゃす!」 ٩( 'ω' )و 「わかります」 ٩( 'ω' )و 「PHP ですね」
  186. ぶっちゃけ React したかった

  187. ٩( 'ω' )و (いつやるの?今でしょ!) ٩( 'ω' )و (今やったら死ぬ未来しか見えない) ٩( 'ω'

    )و (・・・) ぶっちゃけ React したかった
  188. (” ՞ਊ ՞)” 「最小のコストで最大の成果をデスヨ」 (” ՞ਊ ՞)” 「転送量の削減も頼む」 ٩( 'ω'

    )و (いつやるの?今でしょ!) ٩( 'ω' )و (今やったら死ぬ未来しか見えない) ٩( 'ω' )و (・・・) ぶっちゃけ React したかった
  189. (” ՞ਊ ՞)” 「最小のコストで最大の成果をデスヨ」 (” ՞ਊ ՞)” 「転送量の削減も頼む」 ٩( 'ω'

    )و (いつやるの?今でしょ!) ٩( 'ω' )و (今やったら死ぬ未来しか見えない) ٩( 'ω' )و (・・・) ぶっちゃけ React したかった ٩( 'ω' )و 「わかります」 ٩( 'ω' )و 「昔ながらの作りでいきましょう」
  190. (” ՞ਊ ՞)” 「最小のコストで最大の成果をデスヨ」 (” ՞ਊ ՞)” 「転送量の削減も頼む」 ٩( 'ω'

    )و (いつやるの?今でしょ!) ٩( 'ω' )و (今やったら死ぬ未来しか見えない) ٩( 'ω' )و (・・・) ぶっちゃけ React したかった ٩( 'ω' )و 「わかります」 ٩( 'ω' )و 「昔ながらの作りでいきましょう」 CLIENT No jQuery No FRAMEWORK POLYFILL SERVICE BOWER GRUNT SERVER NON-REST API NO XDR SPRING MVC THYMELEAF EMBEDDED TOMCAT 5 年前のアーキテクチャを 少しだけモダンにする
  191. (” ՞ਊ ՞)” 「最小のコストで最大の成果をデスヨ」 (” ՞ਊ ՞)” 「転送量の削減も頼む」 ٩( 'ω'

    )و (いつやるの?今でしょ!) ٩( 'ω' )و (今やったら死ぬ未来しか見えない) ٩( 'ω' )و (・・・) ぶっちゃけ React したかった ٩( 'ω' )و 「わかります」 ٩( 'ω' )و 「昔ながらの作りでいきましょう」 CLIENT No jQuery No FRAMEWORK POLYFILL SERVICE BOWER GRUNT SERVER NO XDR NON-REST API SPRING MVC THYMELEAF EMBEDDED TOMCAT 5 年前のアーキテクチャを 少しだけモダンにする <script src="https://cdn.polyfill.io/v1/polyfill.min.js"></script> POLYFILL SERVICE UA に応じて最適な polyfill が提供される $el.find(selector) → el.querySelector(selector) $.getJSON(url) → fetch(url)
  192. (” ՞ਊ ՞)” 「最小のコストで最大の成果をデスヨ」 (” ՞ਊ ՞)” 「転送量の削減も頼む」 ٩( 'ω'

    )و (いつやるの?今でしょ!) ٩( 'ω' )و (今やったら死ぬ未来しか見えない) ٩( 'ω' )و (・・・) ぶっちゃけ React したかった ٩( 'ω' )و 「わかります」 ٩( 'ω' )و 「昔ながらの作りでいきましょう」 CLIENT No jQuery No FRAMEWORK POLYFILL SERVICE BOWER GRUNT SERVER NO XDR NON-REST API SPRING MVC THYMELEAF EMBEDDED TOMCAT 5 年前のアーキテクチャを 少しだけモダンにする BUILD 安定の GRUNT & BOWER COMPILE CONCAT MINIFY CACHE BURST etc ...
  193. (” ՞ਊ ՞)” 「最小のコストで最大の成果をデスヨ」 (” ՞ਊ ՞)” 「転送量の削減も頼む」 ٩( 'ω'

    )و (いつやるの?今でしょ!) ٩( 'ω' )و (今やったら死ぬ未来しか見えない) ٩( 'ω' )و (・・・) ぶっちゃけ React したかった ٩( 'ω' )و 「わかります」 ٩( 'ω' )و 「昔ながらの作りでいきましょう」 CLIENT No jQuery No FRAMEWORK POLYFILL SERVICE BOWER GRUNT SERVER NO XDR NON-REST API SPRING MVC THYMELEAF EMBEDDED TOMCAT 5 年前のアーキテクチャを 少しだけモダンにする PRODUCTIVITY WATCH & COMPILE & SYMLINK
  194. (” ՞ਊ ՞)” 「最小のコストで最大の成果をデスヨ」 (” ՞ਊ ՞)” 「転送量の削減も頼む」 ٩( 'ω'

    )و (いつやるの?今でしょ!) ٩( 'ω' )و (今やったら死ぬ未来しか見えない) ٩( 'ω' )و (・・・) ぶっちゃけ React したかった ٩( 'ω' )و 「わかります」 ٩( 'ω' )و 「昔ながらの作りでいきましょう」 CLIENT No jQuery No FRAMEWORK POLYFILL SERVICE BOWER GRUNT SERVER NO XDR NON-REST API SPRING MVC THYMELEAF EMBEDDED TOMCAT 5 年前のアーキテクチャを 少しだけモダンにする THYMELEAF ぶっちゃけしんどい HTML フラグメントはツラいよ
  195. (” ՞ਊ ՞)” 「最小のコストで最大の成果をデスヨ」 (” ՞ਊ ՞)” 「転送量の削減も頼む」 ٩( 'ω'

    )و (いつやるの?今でしょ!) ٩( 'ω' )و (今やったら死ぬ未来しか見えない) ٩( 'ω' )و (・・・) ぶっちゃけ React したかった ٩( 'ω' )و 「わかります」 ٩( 'ω' )و 「昔ながらの作りでいきましょう」 CLIENT No jQuery No FRAMEWORK POLYFILL SERVICE BOWER GRUNT SERVER NO XDR NON-REST API SPRING MVC THYMELEAF EMBEDDED TOMCAT 5 年前のアーキテクチャを 少しだけモダンにする THYMELEAF ぶっちゃけしんどい HTML フラグメントはツラいよ CLIENT SERVER HANDLEBARS GRUNT COMPILED TEMPLATES WEB-INF TEMPLATES RENDER TEMPLATE GET NEWS PAGE RETURN HTML RELOAD BROWSER RECEIVE REQUEST GET NEXT PAGE FRAGMENT RETURN JSON CHECK DUPLICATION RENDER COMPILED TEMPLATE
  196. (” ՞ਊ ՞)” 「最小のコストで最大の成果をデスヨ」 (” ՞ਊ ՞)” 「転送量の削減も頼む」 ٩( 'ω'

    )و (いつやるの?今でしょ!) ٩( 'ω' )و (今やったら死ぬ未来しか見えない) ٩( 'ω' )و (・・・) ぶっちゃけ React したかった ٩( 'ω' )و 「わかります」 ٩( 'ω' )و 「昔ながらの作りでいきましょう」 CLIENT No jQuery No FRAMEWORK POLYFILL SERVICE BOWER GRUNT SERVER NO XDR NON-REST API SPRING MVC THYMELEAF EMBEDDED TOMCAT 5 年前のアーキテクチャを 少しだけモダンにする THYMELEAF ぶっちゃけしんどい HTML フラグメントはツラいよ CLIENT SERVER HANDLEBARS GRUNT COMPILED TEMPLATES WEB-INF TEMPLATES RENDER TEMPLATE GET NEWS PAGE RETURN HTML RELOAD BROWSER RECEIVE REQUEST GET NEXT PAGE FRAGMENT RETURN JSON CHECK DUPLICATION RENDER COMPILED TEMPLATE TO BE CONTINUED ...
  197. 適切なタイミングで必要な技術を取捨選択 ビジネスの成長と技術的負債のバランス

  198. まとめ ビジネスの成長と技術的負債のバランスを取る 用途に応じて適切な技術を採用する Java は素早く簡単に作ることも堅く作ることも出来る コンテンツ x テクノロジーで世界一の経済メディアをつくる

  199. ありがとうございました

  200. UZABASE / NewsPicks では 一緒に働いてくれるエンジニアを募集しています 世界一の経済メディアを作りたい人 グローバルな職場で世界展開するサービスに携わりたい人 大量データ / 大量アクセスのあるサービスの開発をしたい人

    若くて勢いのあるスタートアップで働きたい人 エンジニアが自由に働ける会社で挑戦したい人 お待ちしております