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

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

monzou
April 11, 2015

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

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

monzou

April 11, 2015
Tweet

More Decks by monzou

Other Decks in Technology

Transcript

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

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

    月に UZABASE にジョイン 前職は金融系トレーディングシステム開発
  3. ABOUT 2008 年 4 月 創業 従業員数 160 名(アルバイト含む) (NewsPicks

    は 40 名弱) 事業拠点 ・東京 ・シンガポール ・上海 ・香港 ミッション 「世界一の経済メディアをつくる」
  4. =

  5. =

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

    広告記事 PICKER キュレーション・コメント ... ... ... 厳選提携メディア 広告主 外部サイト 編集部
  7. チーム体制 プラットフォーム 各チームが緊密に連携しながら開発中 プラットフォーム(エンジニア)は 現在 10 名程度 ブランド デザイン 編集部

    記事編成 / リコメンド / プッシュ通知 など 編集長です 社長です 編集部員です 職人の手による 温かみのあるプッシュ通知です
  8. Dynamo DB ElastiCache INTERNET DESKTOP PC MOBILE ROUTE 53 ELB

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

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

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

    EIP INCOMING APP BAT Elasticsearch RDS Redshift CloudWatch SNS SES SQS DATABASE
  12. 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
  13. Dynamo DB ElastiCache INTERNET DESKTOP PC MOBILE ROUTE 53 ELB

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

    EIP INCOMING APP BAT Elasticsearch RDS Redshift CloudWatch SNS SES SQS Java OR DIE
  15. 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
  16. 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
  17. INTERNET DESKTOP PC MOBILE ROUTE 53 ELB EIP INCOMING APP

    BAT Elasticsearch Java Applications CMS RECOMMEND ANALYTICS MANAGE DASHBOARD SUPPORT
  18. Dynamo DB ElastiCache INTERNET DESKTOP PC MOBILE ROUTE 53 ELB

    EIP INCOMING APP BAT Elasticsearch RDS Redshift CloudWatch SNS SES SQS APP SERVER
  19. 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
  20. 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
  21. 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
  22. 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 を書くのが面倒くさい
  23. 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)); } }
  24. 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; }
  25. 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 ... } }
  26. 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 がオススメ
  27. 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) 可用性が担保された分散キュー コンシューマはႈ等に実装する必要がある → スループット向上 / バックエンドの分離
  28. 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 ..
  29. 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
  30. 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
  31. Article Java ユーザー毎の タイムライン 取り込んだ記事がタイムラインに反映されるまで Java Java Categorize Queue Propagate

    Queue Feed Service Categorize Service Propagate Service Vowpal Wabbit Sckit Learn etc. Py Dynamo DB ElastiCache (Redis) 機械学習 エンジン
  32. 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 機械学習 エンジン
  33. 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 機械学習 エンジン
  34. 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 機械学習 エンジン
  35. 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 機械学習 エンジン
  36. 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 機械学習 エンジン
  37. 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 機械学習 エンジン
  38. 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 機械学習 エンジン
  39. 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 機械学習 エンジン
  40. 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 機械学習 エンジン
  41. 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 機械学習 エンジン
  42. 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 機械学習 エンジン
  43. 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 機械学習 エンジン
  44. 入社 1 ヶ月が経ったころ ... (” ՞ਊ ՞)” 「Redis が危ないです」 (”

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

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

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

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

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

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

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

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

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

    タイムラインを更新 replication 自前で レプリケーション & バックアップ BAT サーバーは 1 台で処理
  54. 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 台で処理
  55. 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 で パーティショニング
  56. 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 で パーティショニング
  57. 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); } }
  58. 垂直分散後 (” ՞ਊ ՞)” 「これで一安心ですね」 (” ՞ਊ ՞)” 「しばらくはゆっくりとした夜が過ごせるでしょう」 一週間後

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

    'ω' )و (思った通りの結果になったな ……) 一週間後 (” ՞ਊ ՞)” 「垂直分散では負荷が思ったほど下がりませんね」 (” ՞ਊ ՞)” 「人気ユーザーが一部のパーティションに集中しているようです」 (” ՞ਊ ՞)” 「やはりハッシュで分散しましょう」 (” ՞ਊ ՞)” 「リバランスは運用でカバーっしょ!」
  60. 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); } }
  61. ハッシュ分散後 (” ՞ਊ ՞)” 「これで一安心ですね」 (” ՞ਊ ՞)” 「しばらくはゆっくりとした夜が過ごせるでしょう」 二週間後

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

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

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

    処理 replication 自前で レプリケーション & バックアップ Read / Write Read / Write
  65. 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
  66. 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 書き込みは マスターに
  67. 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 読み込みは リードレプリカから (スティッキー) 書き込みは マスターに
  68. 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 読み込みは リードレプリカから (スティッキー) 書き込みは マスターに リードレプリカが 落ちた場合は フェイルオーバー
  69. 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); } }
  70. 入社 2 ヶ月が経ったころ ... (” ՞ਊ ՞)” 「SEO がヤバいです」 (”

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

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

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

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

    SEO 対策まったく無し 秋に編集部が発足 ・独自記事が徐々に拡散されるように ・ソーシャルからの流入は増えてきたが検索から全く流入しない
  75. 翌日 ٩( 'ω' )و 「真面目に作る時間も勿体無いので ……」 ٩( 'ω' )و 「Phantom

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    )و 「わかります」 ٩( 'ω' )و 「リファクタして良いですか?」 ٩( 'ω' )و 「この API って何ですか?」 ٩( 'ω' )و 「JSON に要らないプロパティが大量にあって重いんですけど」 ٩( 'ω' )و 「noinclude っていう変なパラメータがあるんですけど」 入社 3 ヶ月が経ったころ ...
  92. ٩( 'ω' )و 「少しずつ依存を切り離して ……」 ٩( 'ω' )و 「業務レイヤは独立させよう」 ٩(

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

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

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

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

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

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

    )و 「わかります」 ٩( 'ω' )و (もう 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
  99. 年末 ٩( 'ω' )و 「来年は少しずつリファクタしていきたいな」 ٩( 'ω' )و 「まずは大量に出ているエラーメールを撲滅するところから始めよう」 ٩(

    'ω' )و 「来年はコードベースをまるっと変えるのを目標にしよう」 ٩( 'ω' )و 「年明けから頑張るぞい」
  100. 入社 4 ヶ月が経ったころ ... (” ՞ਊ ՞)” 「DAU が伸び悩んでいます」 (”

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

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

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

    ՞ਊ ՞)” 「そろそろ感覚で改善していては駄目なようです」 (” ՞ਊ ՞)” 「チームをつくってグロースハックをして下さい」 ٩( 'ω' )و (今年の目標が早くも崩れた ……) (” ՞ਊ ՞)” 「事業計画上の数値はこうです」 (” ՞ਊ ՞)” 「今日と明日は分析をして来週から数値をこれだけ上げましょう」 ٩( 'ω' )و (スピード感がヤバい) ٩( 'ω' )و (そもそもログは残ってるんだっけ ……?)
  104. KPI DASHBOARD SPRING BOOT + DOMA 2 + ANGULAR JS

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

    + D3 管理画面のようなサクっと作るアプリには オーバースペック気味 …… 分析用のサーバーが Redshift に 格納したデータを取得
  106. 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 正直しんどい
  107. 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); } }
  108. (” ՞ਊ ՞)” 「やっと数値が見られるようになりましたね。喜ばしいことです」 (” ՞ਊ ՞)” 「では早速 KAIZEN しましょう。結果を必ず分析しましょうね」

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

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

    ٩( 'ω' )و 「はい」 ٩( 'ω' )و (そういえば A/B テストとか出来るのかな ……) ٩( 'ω' )و 「ダッシュボードが出来ました」 ٩( 'ω' )و 「ユーザー行動も分析しました」 ٩( 'ω' )و 「やはり ◦◦ の辺りが怪しいです」 その後 当然できなかった
  111. ٩( 'ω' )و 「アプリの文言はサーバーから取得するようにして ……」 ٩( 'ω' )و 「タブ情報もユーザー毎に切り替えられるように ……」

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

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

    )و 「アプリの文言はサーバーから取得するようにして ……」 ٩( 'ω' )و 「タブ情報もユーザー毎に切り替えられるように ……」 ٩( 'ω' )و 「ユーザーをセグメント化して一部ユーザーだけにリリースしたいな」 悲しいのでサクっと改修した ٩( 'ω' )و 「わかります」 ٩( 'ω' )و 「UPDATE features SET roles = 'BETA-TESTERS' WHERE id = 'AWESOME_FEATURE'」 ٩( 'ω' )و 「カチャカチャ・・・ッターン!」 ٩( 'ω' )و (ドヤァ・・・)
  114. (” ՞ਊ ՞)” 「最小のコストで最大の成果をデスヨ」 (” ՞ਊ ՞)” 「地道な努力が必要デスヨ」 ٩( 'ω'

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

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

    )و 「Facebook ページが息をしていないようです」 ٩( 'ω' )و 「メルマガを配信するお金が無いです」 ٩( 'ω' )و 「プッシュ通知が〜」 その後 ٩( 'ω' )و 「わかります」 ٩( 'ω' )و 「取り敢えずメール配信サービス契約しますね」 ٩( 'ω' )و 「リテンション・・・ダイジ・・・AARRR・・・」 闘いの日々は続く
  117. 入社 5 ヶ月が経ったころ ... (” ՞ਊ ՞)” 「ウェブ版がヤバいです」 (” ՞ਊ

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    )و (いつやるの?今でしょ!) ٩( 'ω' )و (今やったら死ぬ未来しか見えない) ٩( 'ω' )و (・・・) ぶっちゃけ 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 ...