Rso's Jotter

日々の開発の知見のメモやその他雑記

2020年振り返り

もう年末なので書いた記事を見直しながら今年一年の振り返りを書いておきます。

2019年合わせて100件にするために、今年は65件書くと言っていたんですが、今年書いた件数は,

23件...

明らかに後半失速していますね。9月以降全く書けていませんでした。 これはブログに書くほどのネタがなかったせいもありますが、来年はもう少し定期的に記録に残したいと思います。

2020年の草

今年はまあまあコード書いてはいましたが、去年に比べると多少落ちていました。 会社では結構自由にコード書いてられましたが、年初、年末は方向性の議論などが多くなったり、 また年末後半は優秀なメンバーが参画してくれたおかげで自分が書く量が減りました。

趣味で書いているコードがあるGitlabの方は去年と同じぐらいでした。(こっちのほうがコミットが汚いので草は多く見えます)

GitHub

f:id:rso:20201230111552p:plain
Github2020草

GitLab

f:id:rso:20201230111524p:plain
GitLab2020草

振り返り

1月

年明けはパリにいたため、正月に日本に返ってくる。

家内は暫くパリにまだ滞在なので、独身無双モード。

2月

独身モードは自由時間があるけど、自由すぎてなかなか時間をうまく学習に当てられない。

会社では新たな組織編成が行われる。前職主催の開発合宿で遊ぶ。

コワーキングスペース巡って作業場所を開拓していた。

3月

コロナの影響で、ついにリモートワーク体制に移行。

当初はとりあえず落ち着くまでの数週間ということで開始。

かなりやりづらさを感じて、無駄に疲れていた。

独身モード終わり。(家内は鎖国される前にパリから帰還)

息子2才の誕生日を迎える。

4月

社内の体制変更があり、インフラ周りを一部担当することに。

主にk8sをキャッチアップ。全然分からん。

Firebase(FireStore)の練習のために、Quizhubというサイトを友人と適当に作る。

5月

インフラ担当のタスクとして、ややこしいk8sのフロントイメージビルドをNetlify側に寄せるような 対応を実施。

社内向けにRedmineのプラグインを適当に作る。

6月

社内の開発体制が紆余曲折合って、なぜか自分一人で引き続きプロダクトの面倒を見ることに。

無駄なコミュニケーションコストが無くなってかなり気楽な時期。

リモート作業が慣れてきた時期。

7月

作ったQuizhubをいじって遊ぶ。SPAのSEO対策に苦戦する。いろいろやったが、あまり効果でず。

8月

なんちゃってTypeScriptをはじめる。

とりあえずNodeJSで動くLambdaとか小さいものを適当に書き換えてみる。

9月

FTXのLegend Campaign に参加して MarketMaker(MM)ボットを作る。 無事達成。

10月

MM bot を他に転用して遊ぶ。

DeFiを適当に触って遊び始める。

11月

会社では今後の方向性の議論が多めになり、開発の手数自体は少々少なめになる。

Defiの情報収集が多くなったが、ごく表面的な部分しか理解していない。

12月

来年から会社でまた組織編成あり。それの準備が多くなる。

総括

コロナの影響により、かなり生活のリズムが変わった一年でした。

会社で継続的に開発ができていたことと、それ以外にもちょくちょく適当な活動ができていたのは良かったです。

ただ、主ににNuxt.jsなどフロントエンド周りなど、すでに慣れている範囲でいろいろ作ることが多く、新しいものを試すのにそこまで時間が割けなかったように思えます。

DeFi周りはごく浅い表面的なことしか理解しておらず、もうすこし突っ込んだ内容を理解したいところです。(片手間でやるには難しい..)

来年も引き続き面白そうなことを触ってアウトプットしていければと思います。

3年ぶりにISUCON予選参加しました

前職の同僚とISUCON予選(ISUCON10)に参加しましたのでその活動メモしておきます。

ISUCONとは

お題となるWebサービスを決められたレギュレーションの中で限界まで高速化を図るチューニングバトル、それがISUCONです。

isucon.net

去年は別の開発合宿と日程がダダかぶりしたので断念し、その前はみんなの家族が出産イベントなどで忙しくて見送り。なので3年ぶりの参加でした。

事前準備

ほとんど準備する時間は取れず、前日に簡単な進め方のすり合わせをしたぐらいでした。今回はNewRelicがスポンサーなので競技中に使用できるライセンスが付与されたので、それを触って遊んでました。

当日のお題

f:id:rso:20200913172153p:plain

今回はisuumoというイスと物件を検索する機能をもったサイトでした。

ざっくりイス検索、物件検索、イス購入、CSV入稿などが用意されていました。 その他、なぞって検索という画面がありました。

f:id:rso:20200913172330p:plain

やったこと

開始時間が10時から12時に変更。 開始直後はベンチマーカーが動かなかったこともあり、みんなでコードを読んで対策を検討していました。

その後ベンチマークを実行したところ、明らかにDBネックな状態で処理が実行されていたので、DBサーバを分離しました。

その後、チーム内で手を入れられたのは以下の内容でした。

  • nginx周り
    • 画像のキャッシュ
    • APサーバ2台にロードバランス, DBサーバを分離
  • webサーバ周り
    • レスポンスをRedisでキャッシュ
  • DB周り
    • インデックス貼りまくる

Redisで一度検索した結果をキャッシュすることで、DBサーバの負荷が下がり、スコアが向上しました。

最終スコア

f:id:rso:20200912215016p:plain

やろうとしてやれなかったこと

  • LIKE CONCAT('%', ?, '%') の文字列LIKE検索をカラムにバラす

ここは自分が担当していた部分でしたが、時間内にベンチマークを通せず断念.. その他なぞって検索の改良は全然手が出ず..

みんなの感想見るまで知らなかったこと

  • MySQL5.7だと降順インデックスが効かない、MySQL 8.0にあげてDESCENDING INDEXにする
    • そもそも降順インデックスが効かないことを知りませんでした..
    • そのほか値をマイナスに変換して無理やり昇順にしたり、Generated Columnを使う方法もあったようです。
  • 位置情報の検索は、SPATIAL INDEXを使うと効果的。

所感

全然歯が立っていないんですが、とりあえず無難な部分を修正してある程度スコアを上げれたので良かったです。みんな久々にインフラチューニングを8時間やって良いリハビリになったかと思います。 やりたいところ思うことがあっても、時間内にベンチマークのテストをパスできず終了してしまったので、実装力が足りないことを痛感します。(上位の人たちはなんであんなに早く直せるんだろう..) 終わった後のみんなの解説見るだけですげー勉強になるので、来年もまた参加できるなら参加したいところです。

SPAのSEO対策でRendertronを使って苦戦しているメモ

前回、 FIrebaseのSPAでOGP対応をしたので、よろしくmetaタグの出力はうまくできるようになったのですが、依然コンテンツがなかなか検索エンジンに認識されないという問題がありました。

検索エンジンに認識されないという問題対策

SPAはJSをまるっとダウンロード、実行する必要があるため、検索エンジンはコンテンツを読みづらいという問題があります。どうすればいいのか調べてみましたが、

  • NuxtのSSRを使用する.
    • いまの Firebase Hostingをやめて、 HerokuなどのPaaSサービスに乗り換える必要がある。工数大。
  • JAMStack的な構成に作り変える。

    • JAMStackって言いたかっただけです。使用しているフレームワークや方式を大幅に変える必要があり、工数大大。
  • Rendertron のような Renderingツールを間に挟んでごまかす。

といった方法があるようです。

Rendertronによる対応は、以下の一休さんの記事で知りました。

user-first.ikyu.co.jp

どうやら、クローラがアクセスする場合は、通常のブラウザアクセスとは異なり、間にレンダラ(Rendertron)をかましてやり、ここがレンダリングした結果をクローラに返しているという方式のようです。通常、この手の課題はSSRを行うことで解消しそうなものですが、SSRを導入することでサーバ側の負荷が上がり、パフォーマンス低下を引き起こした事例がったので、SSRではなく、レンダラを介する手法を取っているとのことでした。

https://developers.google.com/search/docs/guides/dynamic-rendering

Google本家でも、 Javascript実行に時間がかかるサイト(SPA)に対しては、(いろいろと議論はあるようですが)ダイナミックレンダリングを推奨しているようです。

Rendertronの導入、苦戦

Rendertron自体は上記のリポジトリから入手可能ですが、導入や設定において、いくつか苦戦したポイントがあったので、メモしておきます。

Dockerイメージがない

公式ではGoogle App Engineでのデプロイ方法が説明されていて、確かにすぐにデプロイできたのですが、デフォルトのconfigのメモリが4Gになっていて、さらに途中で停止させるわけにはいかないので、無料枠があるとは言えコスト的にちょっとお高い感じになってしまいます。なので安そうなVPSにインストールしようと思ったのですが、残念ながらDockerイメージがなく、新規でインストールする必要がありました。インストール方法はここでは割愛しますが、Puppeteerなどの依存ライブラリにを入れるのに多少時間がかかりました。

Firestore を使用しているページにアクセスするとタイムアウトする

早速Rendertronで試していたいたのですが、レンダリングしたいページFireStoreを使用していたのですが、なぜか必ずタイムアウトします.. おそらくsnapShotなどをFireStoreが待ち受けているので、リクエストが終了したと判断されないような気がします。

このあたりでも似たような問題に遭遇している人がいるようで、とりあえずfirebase.app をDeleteすればいけるとのことです。自分も取り急ぎの、Rendertronからのレンダリングは途中でFirebase.app.deleteを呼んで回避(どうせページ遷移はしないので)しましたが、おそらくもっと良いやり方がありそうです。

それでも遅い

上記の対策によりタイムアウトはしなくなりましたが、それでもレスポンスを返すのに5-7秒かかってしまいました。レスポンスが遅いのがSEO対策上あまりよろしくないので、何かしら改善をする必要がありそうです。取り急ぎRendertronはレスポンスをキャッシュできるので、デイリーで別のところからリクエストを投げて、Rendertronにキャッシュされるようにすることで、レスポンスを改善させました。

試行錯誤はしましたが..

そもそもダイナミックレンダリング自体が議論になっているような話ですが、いろいろ深みにハマっている感が拭えない感じになってしまいました。もっとスマートな方法がないか模索していきたいところです。

TypeScriptことはじめ(環境準備だけ)

TypeScriptは読むことはあっても、真面目に書く機会がなかったので、 少し練習してみました。そのためにイチから始めるための環境準備作業をメモしておきます。

導入

TypeScriptをコンパイルしてJSを吐くための typescript をインストールします。

 $ npm init
 $ npm install typescript

サンプルファイル作成

function hello(name: string): void {
  console.log('hello ' + name + " !")
}

let yourname: string = "Yamada";
hello(yourname)

JSにコンパイル

 $ ./node_modules/.bin/tsc sample.ts  
 -> sample.js が出力される
 $ node sample.js
 hello yamada !

コンパイルめんどくさい時は

ts-nodeを使用すると、コンパイル不要でnodeに実行させられます。

 $ npm install ts-node
 $ ./node_modules/.bin/ts-node sample.ts
 helloyamada !

FirebaseのCloud Functionでデプロイしてみる

nodeでただ単に動かすだけだと面白くないので、 Cloud Functionでも動かしてみます。

と言っても以下のサンプルに追記するだけです。 github.com

typescript-getting-started を cloneして、

'use strict';

import * as functions from 'firebase-functions'

export const helloWorld = functions.https.onRequest((request, response) => {
 function hello(name: string): string {
   return 'hello ' + name + ' !'

 }

 let yourname: string = "Yamada";
 response.send(hello(yourname));
});

これを適当なfirebaseのプロジェクトで以下のように

 $ firebase init functions
 $ npm run deploy 

してやればCloud Function上でも動きました。

Serverlessを使用してLambdaでも動かしてみる

とりあえず動かしてみるだけばっかりですが、ついでにLambdaでもやってみます。

serverless フレームワークの設定が済んでいれば、テンプレートからすぐに実行できるとのこと。

 $ sls create -t aws-nodejs-typescript -p ts-test   
 $ cd ts-test
 $ cat handler.ts 
   ... ここに処理を記述するtsファイルが生成されます
 $ npm install
 $ sls deploy
 $ sls invole -L -f hello
{
    "statusCode": 200,
    "body": "{\n  \"message\": \"Go Serverless Webpack (Typescript) v1.0! Your function executed successfully!\",\n  \"input\": {}\n}"
}

...という感じに、さくっと実行まで行けました(すでにserverlessフレームワークを使用していたので)。

準備完了

Nuxt.jsとかで動いているJSをいきなりTypeScriptにすると負荷がかかりそうなので、 まずはJSで動いているFunction系をTypeScriptに更新したり新規で作ったりしながら、慣れていこうかなと思います。

参考

Nuxt.js(SPA), Firebase Hosting, Cloud Function でOGP対応 その2

以前、 タイトルの構成でOGP対応で試行錯誤した内容を書いていたのですが、少しだけ進化したので、その内容をメモしておきます。

rso.hateblo.jp

以前の方法

QuizHubというWebサービスを作っていたのですが、 SPAなので、 /quizzes/:quiz-id のようなクイズ個別ページのURLにアクセスしたときにOGP情報を含んだmetaタグが出力できないので、Cloud Functionを動かしてOGP情報を含むmetaタグを出力し、 /quizzes/show?id=xxx のようなURLにリダイレクトするというやり方をしていました。このやり方では以下のような問題がありました。

  • リダイレクトされたURLをそのまま共有すると、そのURLはOGPがでない。
  • Cloud Functionがそこそこ遅い。

この内の1つ目は、SPAフォールバックの仕組みを理解していなかったので、このへんをうまくやると解消できました。

SPAフォールバックとは

/quizzes/:quiz-id のようなURLはSPAだと実際にこのURLにファイルが存在するわけでないので、vue-routerなどから遷移はできても、リロードしたりURL直指定でアクセスするとページが404になってしまいます。このような場合、 このURL直指定での404を SPAのルートのindex.htmlを返してやることにより、ページを表示させてやることができます。

Nuxtだと以下のように fallback: true を入れてやることにより、SPAフォールバックの設定ができます。

ja.nuxtjs.org

export default {
  generate: {
    fallback: true
  }
}

(catch-allフォールバックとも言うんですかね、正式な定義が分かっておりません..)

Cloud Functionを用いたSPAフォールバックでOGP対応

今回はこのSPAフォールバックを用いて以下のように変更しました。

  1. SPA生成(nuxt generate)時に出力したSPAのルートファイル(index.html)をCloud Functionにもデプロイ
  2. /quizzes/:quiz-id のようなOGP出力したいページにアクセスしたら、 Cloud Functionを起動して、 OGP metaタグを加えて、index.htmlをレスポンスする。

Cloud Functionでの実装のだいたいこんな感じです。

module.exports = functions.region('us-central1').https.onRequest(async function(req, res) {
  // SPAで生成したindex.htmlを読む
  const html = fs.readFileSync("./index.html", 'utf8');

  const dom = new JSDOM(html)
  const document = dom.window.document
  const headElement = document.hea

  /* ...(省略) JSDOMで metaタグを書き換える */

  const updateHTML = document.documentElement.outerHTML

  // 生成したHTMLを返却
  res.set("Cache-Control", "public, max-age=600, s-maxage=600");
  res.status(200).end(updateHTML)
}
   

こうしてやることで、OGP情報を加えた状態でSPA、フォールバックをすることが可能です。 相変わらずこんな方法で正しいのかという気はしますが、一応目指した挙動はしています。

課題

上記の方法により、前回はリダイレクトによるURLが変わってしまう問題は改善できました。(依然Cloud Functionでそこそこ遅いという問題がありますが) また、OGP情報が出力されたとは言え、Google からはほとんどクローリングしてもらえない状態なので、SEO対策は Rendertron などを 使用して別途対策する必要がありそうです(この方法はまた別の機会に書きたいと思います)。