キャッシュフレンドリーなステートレスアプリケーション設計について考える #CDN_Study

CDN_Study という勉強にいってきた。 https://http2study.connpass.com/event/81469/

そこで、Akamaiの方が、「個人の意見だけど、アプリケーション側がもっと基礎設計でステートレスでキャッシュフレンドリーな設計になってないといけないよね」という旨の発言をしていて、最近そのことにアプリケーションエンジニアとして同じようなことを考えていたので、書き出してみる。

SPAとかSSRとかフロントの不毛な話は出さないようにしてるが、主にサーバレス環境を意識している。

前提

  • 世の中のアプリケーション内のモジュールは、Statefull or Stateless に分類でき、それをツリー状に表現できれば差分検知できる、という React の仮想 DOM 的な世界観が自分にある
  • 以下の話は、基本的には Fastly のサロゲートペアーとそのためのミドルウェアを作れば既に実現できる話ではある
  • (Akamaiは自分は使ったことないです。ごめんなさい…)

参考: https://docs.fastly.com/ja/guides/purging/single-purges

ウェブアプリケーションへの需要/要求

現状のウェブアプリの設計において、動的ページの HTML をキャッシュさせるのは、それ専用の設計をしないととても難しい。まず、何が入力に対してべき等で、何が外部IOを含んでいるかを事前に分類する必要がある。

で、それを乗り越えると何が嬉しいか。PWAやAMPみたいなフロントエンドからみたパフォーマンスチューニングでは、ある一定以上の体験を求めると、キャッシュ済みの *.htmlCDN のエッジに配信させる必要がある。基本的にアプリケーションサーバに飛ばしたら負け。ここで同意が取れない人もいると思うが、そういう前提とする。

HTMLをエッジに配信させると、体感値としては、1000~5000ms ぐらいのオーダーだったものが50~150ms のオーダーの世界になる。このインパクトは大きい。しかし現状そのアーキテクチャを取るための手法は発達していないので、誰でも出来るといった状況にはない。

具体的にあったらいいなと思うもの

fastly にはサロゲートペアーという機能がある。これはパスに対してタグを打っておいて、そのタグがついてるものをまとめてパージするというもの。

これを使って、たとえば /article/1 というURLがあって、HTMLレスポンスが変える時、そのページのキャッシュ判定を行うサロゲートペアーは次のようなオブジェクトで表現できるかもしれない。

var Relations = {
  '/': ['APP_VERSION:0.0.1', 'app.js', 'app.css'],
  '/article/1': [
    'APP_VERSION:0.0.1',
    'app.js',
    'app.css',
    'function:getArticle?id=1:update_at=1523717246877'
  ],
  // ...
}

まず、デプロイの度に全破棄するような実装のために、 APP_VERSION みたいなキーを張っておく。 他は、ある他のリソースに対して、サロゲートキーへのリレーションの表現をする。

app.js, app.css に違和感はないだろうが、3番目のサロゲートキーで update_at をキーにしたものを登録することで、なんらかの function(AWS Lambdaなどを想定) へ渡すまえにキャッシュ判定をして、CDN内で外部IOが発生することなく高速にキャッシュを返せる判定ができるはず。

これをどのように実装したいか。たとえばこれがDBアクセスを仮定すると、基本的には何らかのDBのレコードのtimestamp を収集することになると思う。

これを実現するような、Node.JS の擬似コードを書くならこう。

// なんらかのORMの post-update を想定
Article.onUpdate(item => {
  AssetCache.invalidate('function:getArticle?id=1:*')
  AssetCache.registerNewSalogateKey(
    'function:getArticle?id=1:' + item.update_at
  )
})

あえて冗長な表現をしている。

で、事前に宣言した Relations に対して、 invalidate したアセットに関連するものを再帰的にパージすれば、「id=1 で表現した入力とその結果が本当にべき等なら」、これで更新後に最初の一回だけ function を実行してあとはキャッシュを返し続けるような実装が、たぶんうまくいく。新規デプロイ時はAPP_VERSION更新すれば全部消える。function を個別に管理するならAPP_VERSIONも不要。運用難しいけど。

ここがうまくいくと嬉しいのは、キャッシュルールはデプロイごとに静的として、サーバーは単方向に invalidate を発行し続けるだけ、クライアントのリクエストも常に同一、動的なのはキャッシュミドルウェアのKVS的な内部状態だけで、デプロイまたはDBのレコード更新に動的に反応するだけのステートレスなアプリケーションが出来る。

こんな実装は可能なのか

構成としてはこうなるだろう。

Function(Application) <=> CasheMiddleware <=> CDN <=> Browser

上に述べた実装を仮に CasheMiddleware という名前にしたが、こいつで上のサロゲートキーのリライトルールを実装する。内部ステートは大雑把に redis かなんかで作れる。転地インデックス作るぐらいでそんなに難しくない。

Fastly のキャッシュパージ速度は公称150msなので(正直実測はしてない)、一時的な不整合も最小限に抑えられる。CloudFront は10分ぐらいかかるので、そうもいかなかったりするが。

それで、実際にはアプリケーションコードから、関連サロゲートキーフレームワークの機能として、動的に決定できるようにする。たとえば、アセットは自明として、なんらかのビューライブラリで

<x-article-component data-json={ useCache(fetchArticle(1)) }/>

みたいなヘルパをかまして、中のエンドポイントを収集してヘッダに埋め込むなりして紐付けたうえでViewとそのリソースへのリレーションを生成すればいい。

この実装を作る気があるか

NO

なぜならこの設計はfastlyのみで動くサロゲートキーの仕組みに強く依存している。fastly でしか動かないものを作りたくない。(個人的な話で言えばフリーランスなんで fastly が使えないと最適化できませんみたいな人材になってしまうのが一番のリスク)

また、セッションを考慮してないので、クッキー依存の動的ページをJSまたはwebcpomonts/iframeなどに必ず切り離さないといけない。そうしないとちょっと前のメルカリで他人のクレカが見えちゃった的なミスが起こる。JS書く人間には抵抗はないが、これを受け入れられない人も多いだろう。だいたいHeaderの右側のログイン情報とか。

また、全てのAPI実装が関連するレコードのタイムスタンプに対してステートレスである、という制約が厳しく、運用で事故る未来しか見えない。ここは人間を信用してはいけないレイヤー…。

しかし、ApplicationServerでHTMLを作って返すより、CDNエッジからHTMLを返した際に得られる速度はわかりやすく圧倒的なので、「fastly のサロゲートペアーまたはそれに準ずる仕様が何らかの標準にあるなら」、「フレームワーク組み込みでサロゲートキー生成を込みで」実装する恩恵が出てくる。ここまできてはじめてフレームワークかなんらかのヘルパ作って嬉しくなるなぁ、という感じ。そうなったら 特定のCDN 使わなくても nginx middleware とか書けばいいし。

ただ、「こういうカスタマイズ要素を標準化しちゃうと、各プラットフォームの売りがなくなっちゃうよね」みたいな話があるのもわかる…ビジネス難しい…

個人的には、CDNはキャッシュのパージ速度、IOへの課金、ストレージサイズへの課金で選ぶので、DevelopperExperience 以外の内部拡張がどうこうって話は正直知りたくすらないみたいな気持ちもある。あるいは世の中すべてが fastly になるならサロゲートキーにロックインされに行くぞ!ってなりますね。

終わりに

なんかこういうの既にありそうだけど面倒で調べなかった。 キャッシュファーストなアプリ設計、初心者がハマりがちで、あんまり流行らなさそうという実感もある。 パフォーマンスチューニングおじさんしてると、諦めてスクラッチで書き始める。辛い。

プロトタイピング

Path of Exile ってハクスラにはまってるんだけど、ゲームってやってたら作りたくなりませんか。 途中まで作った。

https://infallible-easley-5955b5.netlify.com/

使ったもの

  • React
  • Redux
  • SVG

ひたすら素朴な react-redux で作るのを心がけた 他はすべて気合で0から実装

実装したもの

  • 経路探索
  • カメラ
  • ポインタシステム
  • 当たり判定

詰んだ

きれいな設計にするために副作用起こせる場所とアクセス絞ってたら無限に想定から外れて、「絶対に副作用を起こさないと詰む」みたいな箇所が増えてしまい、コードを大幅に書き直さないといけなくなったので、一旦断念。オブジェクト間のメッセージシステムをもっと念頭に置くべきだった。

React部分で困ったことはない(複雑なものもない)。当初懸念していた reducer の実行コストと描画コストだが、pure つけまくるとどうとでもなった。

とはいえSVGをマップ描画すると、描画コストが想定よりだいぶ重かったので、諦めて Canvas で描画して data URI をimage要素にぶち込むと速かった。

近況

ローソンのブランパン(糖質2.0g)、ビーフジャーキー、サラダ、炭酸水…

年末に太った 69kg を 65.kg まで落とした。身長170cmなので平均は62kg。72kgで準肥満。

完全に無の食生活だが、特に空腹感はないし、わかりやすくどんどんやせる。高校生時代の58kgまで落としたい。

ブランパンは本物の虚無の味がする。

TODO: 最近の活動のまとめ書く

ServiceWorker as a Service, または Universal ServiceWorker という発想

ServiceWorker とは本質的に リクエスト&レスポンスモデルであるので、それをサーバーサイドで実装で一種のサーバーロジックとして動かしてしまって良いはずだ ー

という発想に目から鱗だったので、ちょっと考えてみたいと思う。

www.publickey1.jp

ここで試せる。

https://cloudflareworkers.com/#a9bc9ef6b4248289c71518581df30bc7:https://tutorial.cloudflareworkers.com

Cloudflare はCDN業者なので、 それに特化して Service Worker as a Service みたいな表現はしていないが、実態としてはサーバーサイド ServiceWorker だ。Fastly では varnish のミドルウェアなどでキャッシュ破棄設定のロジックやリダイレクトを書いていたが、それが ServiceWorker という体を取っている(ように見える)。

ちゃんと使い込んでいないので、おそらくだが、 cache オブジェクトの実装が cloudflare の asset cache の実装にそのままつながっていて、そこでキャッシュ破棄、構築命令に翻訳されているのだろう。


追記: 実際はレスポンスを書き換えるだけで、まだキャッシュ実装はないが、やりたいとのこと


Cloudflare の手法で優れているのは、現状、すべてのブラウザが Service Worker を実装しているわけではない(というかIEのことだが)ので、将来的には標準的な手法になるはずの中間層を用意したことで、たとえばpush イベントも(Safariであっても) をサーバーサイドで溜め込んで置いたり出来る。また、クライアント用 Service Worker と密に連携したスクリプトも配信できる。ビジネス的にもストレージサイズやイベントドリブンなエンドポイントの実行回数で課金できる。

イベントドリブン標準としての ServiceWorker

ミドルウェアでなんでもできる、という点で AWS Lambda や Google Cloud Function と同じようなものとして使うことが可能だろう。FaaS の1スペックとして、ServiceWorker as a Service という形式はアリだろうか。

cloudflare の チュートリアルで配布されてるコードはこんな感じ。

addEventListener('fetch', event => {
  event.respondWith(fetchAndLog(event.request))
})

/**
 * Fetch and log a given request object
 * @param {Request} request
 */
async function fetchAndLog(request) {
  console.log('Got request', request)
  const response = await fetch(request)
  console.log('Got response', response)
  return response
}

普通の service worker のコードだ。普通の。

毎度 Lambda と Cloud Function を使ってて思うのは、いまいち Node 側からのインターフェースが仕様化・共通化されていないので、毎回ベンダロックインされたコードを書くことになってしまっている。それぐらいだったら一応は仕様がW3Cではっきりしている ServiceWorker の仕様を使うのは筋悪ではない、と思う。

しかしどうせ各プラットフォームが独自な名前空間を持ってイベントを生やしてくる。Cloudflare もその方向を最初検討したようだし、それ自体は現実的なユースケースを踏まえると必要だと思う。実際ユニバーサルな ServiceWorker というものは、なんらかの抽象化層を経ないと到達し得ないだろう。

また、express で cluster モジュールなどを使ってOSSなサーバーサイドServiceWorkerを実装するのは、そこまで難しくなさそうに見える。Open FaaSなどとも方向性が同じになる。

なので、方向性としては歓迎したいが、大きなプレーヤーが標準化に向けて動いてくれないと難しそう。

フロントエンドの負債と向き合う

某所で書いたものを公開用に書き直したもの

前提

フロントエンドでTDDは難しい、というかほぼ不可能である。なぜなら事前に副作用をデータとして表現できるか不明だからだ。たとえばあなたのプロダクトの画面の何処かにボタンを追加するために、その内部表現を事前に思い浮かべることが可能だろうか?

react-redux などのFluxフレームワークは如何に副作用をアクションとして表現することで、テスト・デバッグのための情報を残すか、という視点で発展してきた側面がある。あの冗長なアクション定義は、全てデバッグのために書いていると言っても、過言ではない。それすら「Textは文字がある」といったトートロジーなデータになりがち。

フロントエンドの現実的な単体テストは、他の開発者のために、自分が書いたコードの要求を満たしているか検知する手段として、防衛的にテストアフターしておく。これぐらいしか現実的な手法がない。それ以上は、手動テスト、E2E、あるいはUXデザインの範疇だ。

GUIの開発は、最終的に目に見えてるものがすべてであって、DevTools上のパフォーマンス上のメトリクスではなく、「ユーザーがどう感じたか」に集約される。そして人間の感性は理不尽だ。その理不尽さに付き合うために、どの部分を外部から入力可能な値にしておくか、が設計の妙になる。

要は、UXは単体テストの範疇外だが、そのための変数は外部入力で受け付けるべきだ。

負債と向き合う

よくある状況

このデッドロックを乗り越えるためには、実際必要なのは「気合い」である。ただし「気合い」にも種類がある。より効率が良い「気合い」を目指すべきだ。

とはいえ、最初にいうべきは次のような言葉になる。

「このような状況は会社、開発チームとして溜め込んだ負債であるからして、この改善には痛みを伴う」

残念なことに、まずこれを認めることから始めなければならない。自分も、チームも。

手段

負債と向き合うための現実的な施策として、次のような感じになるだろう。

  • 手動テストを密にやる
  • 書き換えたコードを一部のユーザーにだけ徐々に適用していく
  • エッジケースのエンバグを一時的に許容してもらう
  • (厳密にはリファクタリングではないが)コードの簡略のために仕様を変更することを認めてもらう
  • そもそもコードを破棄して作り直す

個人の裁量でやれるのは、おそらく最初の手動テストの厚さだけで、それすら外部テスターがいる場合にはそれを共有する必要があり、要は大抵は「政治」が付いて来る。誰を説得するか、それもリファクタリングに必要な手続きだと思うしかない。

とにかく手動テストを繰り返しながらコードを分解する。

破棄して作り直すかどうかは、プロダクトの成長を一時的に止めることになるので、それが許容されるかどうかはプロダクトの事情による。見積もり次第。

一つの指標として、暗黙知を多量に要求するものは困難なので、他人が書いた jQuery は作り直した方がいい。

リファクタリングの仮のゴール設定

最初の単体テストの話をした理由だが、自分は大抵、「単体テストが書ける」という状況を、リファクタリングの仮のゴールに設定する。単体テストが書けるということは、不要な依存が切れて、モジュールの境界面が自明になった状態にたどり着いたということである。

備考: これは最近までモジュールシステムが存在しなかった、 JS ならではの事情による。長く開発されてきたプロダクトは、グローバル変数渡しや、自前のDI機構が残っていることだろう。

正直な所、「単体テストできるような状況」を目標にすればよく、実際に単体テストを書く必要はなかったりするのだが、わかりやすい成果物としてユニットテストを追加することが多い。この状態に至ってから、目的を達するコードを書き始めることができる。パフォーマンスだったり、ボタン追加だったり。

ただ、そういうテストがメンテされなくなって久しくなったときに、捨てられるかどうかの判断をできる必要はある。担当者が去ったテストが、カーゴカルト的に維持されてることは多い…

できることからやる、ために小さく分解する

大変なものを大変だと言いながらやる前に、痛みが少ないものからまず向き合う。外堀を固めよう。

フロントエンド開発で、最初に考えるべきは、

  • lint ルールを追加する
  • コードフォーマッタを入れる
  • 型を書く

これらは比較的痛みがない。まず eslint を導入して、CI に入れる。そしてルールを思いつくだけ書く。(各プラグインのrecommendedを全部突っ込むなど) 現代では、ホワイトスペースや改行ルールは人間が考えるものではない。prettier の導入で終わり。

経験上、この3つがわかりやすく効く。

  • globals: 使用可能なグローバル変数の列挙
  • no-unused-vars: 未使用変数の禁止
  • no-unreachable: 到達不可能なコードの禁止

ES Modules がある現代では、特殊な事情(ライブラリが勝手に生やしたり、期待しているもの)がある場合を除いて global 変数は一切使わずにコードが書けるはずだ。その場合も部分的な lint 無効化で対応できるはず。

(この節は、人によって意見が異なるだろう)

現代のJavaScriptにとって、一定以上のプロダクトで静的型は避けては通れないテーマだと思っている。フロントエンドは前述したように自動テストが困難なので、静的解析で出来る限りカバーするだけ事故が事前に検知できる。

とくに静的検査で強烈な開発体験の向上として現れるのが、React の JSX の props に対する静的検査だ。プロパティを変えると、大抵呼び出し元のプロパティ名を修正しないといけない。または自分の想定以上に使われていて、その修正漏れなどが発生する。手元でコードを書いていてもうっかりするから、ましてや他人のコードなど考慮外である。

JSXの発明の偉大な点の一つは、テンプレートに対する入力値を宣言的に記述することで、事前条件を明示したことにある。React以外の他のフレームワークも、現代的なテンプレート、コンポーネントは、ステートレスな関数的な振る舞い、またはステートフルであることが明示されたオブジェクトの形をしているはずだ。また、Component という単位にしたことで、依存が明示的になる。

ただ、痛みのない型の導入、は難しい。というかコツがいる。

flowtype で @flow アノテーションを段階的に追加したり、 typescript の allow-js モードで一旦全部tsコンパイラ管理下に入れてしまって、段階的に拡張子 .ts に置き換える、などの手法がある。

ただし、これはプリコンパイラが前提にある。プリコンパイラがない場合は、まずその導入からはじまる。これはデプロイワークフローに絡んでくるので、なかなかに辛い。ステージング環境を用意して、何度もトライアンドエラーするしかないだろう。

辛いものは辛い。そこは認めるべき。

リファクタの諦めが発生する原因

自分が経験した状況としては…

  • 元のコードの担当者が忙しい or 退職していて、レビューが受けられれない
  • そもそも仕様が↑の脳内にしかなく、逸失している
  • コードが予想もつかない場所で使いまわされていて、テスト範囲が膨大になる(駄目なDRY)
  • 特に仕様が決まっていなかった部分を、一部の人間(開発側またはユーザー両方)にバグと認識されてコミュニケーションコストがかさむ
  • プルリクエストが巨大化 or 細分化され、ブランチの維持で手一杯になるうちに心が摩耗していく

全部辛い。辛いものは辛い。 辛い時は辛いと言う。アラートを上げることは恥ではない。

リファクタリングの目的

プロダクトの改善の為、という大義名分は立てやすいが、そもそも何のためにリファクタするのか、見失わないように意識しておく。

  • 機能追加の速度を上げる
  • バグ抑止
  • 人員追加のためにキャッチアップコストを抑える
  • パフォーマンスチューニングの下準備
  • 自己満足
  • 採用アピール
  • 離職対策

後半の理由は後ろ向きだが、表向きの理由とは別に、現実的にこの目的で行われるリファクタリングは多い。自己満足を含むことが別に悪いことではないが、ただ過度に自己目的化してないか見つめ直す必要はある。過剰な抽象度を備えてしまうと逆に品質は悪化する。

中長期的なリファクタリングは、そのイテレーションの間で発生した現実的な泥臭いユースケースを踏まえ、プロダクトの向かう方向性を描きながら自分たちの事情(ドメイン)をコードで表現するとよい。

自分はフリーランスとしてパフォーマンスチューニングの案件を受けることが多いが、何にせよ最初にやることは大抵リファクタリングである。パフォーマンスの伸びしろは、ほとんどの場合、きれいなアーキテクチャによって担保されるからだ。そして、機能追加の速さも一つの「速度」ではある。

まとめ

リファクタは大変だがやりがいがある。やりすぎは危険。需要に応じて段階的にアーキテクチャを変化させていくとよい。

おまけ

Slay the Spire: 細かすぎて伝わらない攻略メモ

ちょっとかじったら読めるようになるもの。 カードの詳細は http://slay-the-spire.wikia.com/wiki/Slay_the_spire_Wiki にて。

アプデで頻繁に変更が入ってるけど現時点の話。

基本的な考え方

一回のプレーでなんでもやろうとしない。どうせ30分から2時間程度終わるので特化ビルドを狙って高速で死んだほうが学ぶものが多い。

優先順位は Energy > Power > Draw > 0 Cost カード > 状態異常 > ダメージ > Block

Block は意図して稼ぐものではなく、Draw カードの副次効果 や Power によるパッシブ効果で、ダメージ効率を稼ぐついでに得るもの、とした方がいい。そのメカニズムが完成するかどうかで Stage 3 のラスボスを倒せるかどうかが決まる。(狼ボスだけは弱いので倒せたりするが…)

大抵は自分が強すぎるかジリ貧でたどり着いて瞬殺されるかのどっちか。

デッキの圧縮

最初の Strike や Defence はかなり弱い部類なので出来るだけ早めに破棄して強いカードに入れ替えたい。他のカードを引きやすくなると思うと相対的に強くなる。ドミニオンで礼拝堂が強いのと一緒。

イベントやショップでは、自分は大抵、攻撃カード、防御カード、攻撃カード、防御カードと廃棄している。ランダムイベントでのトランスフォーム(実質ガチャ)の対象でもある。

Shopのマスでは1回につき1回だけ金を払ってカードを破棄できる。コストは 75 => 100 > 125 > 150 と上昇する。廃棄のチャンスは少ないので積極的に金が溜まったタイミングで訪問を狙いたい。

ただし、 Ironcrad で Corruption を引いた場合は 防御カードが Exhoust になるので廃棄しない。Vampireイベントで Strike を Bite(攻撃の度にHP+2回復) に入れ替えられるときは大抵やる。このゲームは最大HPよりHP回復手段の方が貴重。

Power や Exhoust カードは使うと山札からなくなるので実質圧縮になる。ドロー系のカードも圧縮になるが、Energyを余らせてるどうかで考える。

圧縮が上手く回ってるときは積極的に 0 コストカードを増やす。

篝火の Smith はカードを増やさず質を上げるので圧縮みたいなもの。ドロー系、序盤は Weak や Vulnerable などの状態異常の手段を優先する。

Ironcrad の Armaments+ は手札の全てのカードをアップグレードできるので実質圧縮みたいなもの。そして共通カードの Apotheosis があるならドロー系以外の強化は一切不要。初手はひたすらドローして Apotheosis をひねり出して使う。0コストでこれは正直強すぎる。ショップで見かけたら最優先で買う。たぶん220金ぐらい。

追記: 数日前のパッチで Apotheosis が 2(1) コストになっていたとのこと

狙いたい Boss Relic

ボス倒した時の入手Relicは比較的入手しやすい。 他のRelicの入手ランダムなので、何かのRelicに依存したビルドを最初から狙うのはおそらく機能しない。

Sozu

Potion が使えなくなる代わりにEnergy +1 / Turn 後半は Potion が相対的に弱ってるのでボスレリック取る頃なら自分を 1.33倍強化したほうが遥かに強い。

Cursed Key

2ボスで取得する場合、確定Relicマスが1個あって実質 Curseが +1枚で Energy +1 / Turn と考えると美味しい。終盤は Curse 一枚無視できるぐらいデッキが回ってるはず。

Pyramid

毎ターンのドローが -1 枚される代わりに、ターン終了時に手札を捨てなくて良くなる。 とくに2~3の高コストカードを任意に使える用になるのが大きい。

おすすめのカード: Ironcrad

Corrpution

すべてのスキルカードが Exhoust になりコスト 0 になる。高速な圧縮手段でもある。 ただし Skill 以外の Block 手段を確保しておかないとBlock手段を失って死ぬ。育ちきってない序盤に手に入れるとありがち。

ドロー+BlockのShrug It Off などが相性がいい。Barricadeと組み合わせるとたぶん負けることはない。どっちもレアだが。

Barricade

毎ターン Block がリセットされなくなる Power。単純に強い。事故らなくなる。 毎ターン Block -15 で維持というレリックで代替できることもあるが、そんなに手にはいらないので狙う必要はない。

これが手に入ってるときは 1コストで Block = ダメージの Body Slum を狙いたい。

Pommel Strike

コスト1 で ドロー1 + ダメージだが、強化すると ドロー2 になる。1コストでドロー2は強い。レアリティが低く序盤で手に入りやすいので序盤で手に入れて強化したい。

Battle Trance

そのターンそれ以上ドローできなくなる代わりに、0コストでドロー3(4) 他のドロー系を使い切ってから最後のひと押しに使いたい。

おすすめのカード: Silent

Neutralize

初期から持ってる 0 コストで ダメージ + Weak1(2) 1回強化すると Weak を切らさず回せるようになるので、他の Weak 手段を切れる。 ダメージもそこそこあるので、あと一発という痒いところに手が届いたりする。篝火で最優先で強化したい。

Ironcrad の強みが初期の戦闘ごとに HP回復6 の Relic なら、 Silent の強みは このカードの存在だと思う。

Noxious Fumes

毎ターン 2(3) の毒ダメージを敵全体に与える。 長期戦に強いのだが、Str強化を持ってる敵を前にするとダメージ出力が回る前に殴り負けるので注意が必要。

おそらく初期にアンロックされてるカードの中だと自然と主力になる。

Backflip

1コストでドロー2しつつ Block+5(8)。バランス良く強い。SilentはEnergy回復手段が豊富なので、何枚あってもいい。

Footwork

Dex +2(3) Dex+の手段は貴重。Backflip などと組み合わせてると良い。

Accuracy

Shiv のダメージを +3 の Power。 Cloak and Dagger や Infinite Blades と一緒に使いたい。QuickStepと組み合わせると手数で高速に Block を稼げる。Exhoust に反応する Passive とも相性がいい。

Unlock がやや遅いのが難点。

Bullet Time

それ以上ドローできなくなる代わりに 3(2)コストでそのターン Skill が使い放題。ドローしきってから使うか判断したい。

デッキ構築ローグライクダンジョン の Slay the Spire が面白かったので紹介したい

デッキ構築 + ローグライク + ダンジョン。

アーリーアクセスで少々コンテンツが足りない感はあるものの、とても面白かった。日本語の wiki や攻略情報が現在ほぼ一切存在しないので、布教兼ねて紹介したい。

プレイアブルの2キャラでクリア済み。クリア = Stage 3 のボス撃破としている。

Slay the Spire on Steam

https://i.gyazo.com/419ab928c2cc0060598b624a9c0d2f93.jpg

概要

基本的にはドミニオンのようなデッキ構築ゲーム。最初のカードはキャラクターごとに固定、毎ターン +3 Energy で 5 ドロー、Attack|Power|Skill のアクションカードをコストを払って実行する。

カードは敵を倒したりショップで購入することで手に入れる。他のデッキ構築ゲーと一緒で圧縮が重要で、イベントマスのストアで金払って1枚廃棄したり、特定のイベントで廃棄したりできる。休憩ポイントで各カードは一回だけ強化できる。

英語か機械翻訳の日本語で、簡単な英語なので英語でやればいいのだが、カード説明の Exhausted が 「その戦闘内での廃棄」に気づくのに時間がかかった。

良さ

最近は自称ローグライクが氾濫しているが、プレー感覚はしっかりとローグライクで、ビルド 最初から理想のゴールを決めて突っ走るか、その場で決めていくか、思い通りにいかない感じが絶妙に歯痒くてよい。

自分はこの手のゲームだと Faster Then Light が一番好きなのだが、プレーのリズム感は似ている。馴れてくると1プレー1時間ぐらいでテンポが良い。

キャラクター

The Ironcrad

戦士系っぽいイメージ。 初期から持ってる、戦闘終了ごとに HP6 回復のレリックが強くて結構無茶できる。

最初にクリアしたときは Felix(そのターン中のみダメージ+4) Heavy Blade(固定ダメージ+ダメージ増加倍率が3倍の追加ダメージ) 以外をドロー系で固めて、Felix をブン回すことで高倍率の Heavy Blade で最大ダメージを叩き出す感じだったが、二回目のクリアは Barricade(ガード値がリセットされなくなる) と Heavy Slum(現在のガード値だけダメージ)のビルドでクリアして、やる度に別のプレー体験になるように多様なビルドが可能。たぶんいちばん調整されている。

Corruption(全ての Skill カードが Exhaust になり、コストが0になる) が何やるにしてもとにかく強い。ただし入手確率も低い。強すぎるので調整されそうな気がする。

The Silent

毒使いのアサシン。脳筋よりビルドがピーキーで上級者向け。 Ironcradと違って回復のレリックがないので、慎重な立ち回りが要求される。

敵の得手不得手がはっきりしてるので、序盤のユニークに手も足も出ずにやられることもしばしば。毒ビルドは強いのだがダメージ出力が出るのが遅いので、その間に防御値を稼げずに負けることも多い。

結局ダガービルドでクリアした。ダガービルドは結構回さないとカード自体がアンロックされないので、それまでが辛かったが、Shiv のダメージ上昇バフを使って Shiv のドローに全部を賭けるビルドが完成したときは強すぎて負ける気がしなかった。

今後にも期待

さすがに2キャラは少ないのだが、それでも30時間ぐらい遊べたし、まだまだやり込めそうではある。自分は30時間ぐらいやった。 追加予定の3キャラ目や、追加のボス、レリックなどに期待しつつ、正式版まで追加要素追うだけでもニヤニヤできそう。

サクサク出来るデッキ構築ローグライクでリプレイ性も高い。おすすめです。