旧gaaamiiのブログ

間違ったことを書いている時があります。コメントやTwitter、ブコメなどでご指摘ください

ISUCON9(予選)の振り返り

今年も、 id:karur4n と二人で、くもキャストチームとしてISUCON9に参戦しました。楽しかったですが、今年も予選突破できず悔しい。

スコアの発表を見ると、

2,600  くもキャスト

でした。

isucon.net

例年、我々のような弱小チームに語れることなどない...という気持ちであまり真面目に振り返っていなかったのですが、我々のような弱小でも弱小なりに楽しめるんだぞというのが(まだ出たことない人などに)伝わればいいかなと思い、振り返りを書いてみました。

※ISUCONってなに?という方はこちらをご覧ください。

qiita.com

お題

毎年楽しみなお題ですが、今年はIsucariでした。椅子を販売できるサイトらしい。

f:id:shgam:20190908003340p:plain

開始時間の変更

ポータルサイトに不具合があったようで、10::00 -> 10:10に変更されました。我々は、Discordで通話してたんですがマイクの入力がイヤホンからになっていたのでMacの内蔵マイクにしたりとかそういうことをしていました。

Alibaba Cloudコンソールでイメージ展開

迅速に問題は解消されて、予告通り10:10に開始しました。

今回は、予選マニュアルの通りにAlibaba Cloudのコンソールでサーバーにイメージを展開するところからのスタートでした。前回までのISUCONは特にサーバーのセットアップが必要なく、ポータルサイトに入るとチームに与えられたサーバーのIPが表示されているので、そこにsshで入って作業を開始するという流れでした。

設定・起動を終え、サーバー名とGlobal IPとPrivate IPをポータルサイトに登録、Alibaba Cloudのコンソール画面でログインのために作成/設定しておいた鍵を共有、ローカルの/etc/hostsを編集して起動したIsucariの画面を確認するなどしました(10:30)。

f:id:shgam:20190908002040p:plain

ソースコードをプライベートリポジトリへ

id:karur4n に、Githubにプライベートリポジトリを作成、ソースコードを置いて、Contributorに追加してもらいました(10:48)。

使う参考実装はNode(TS)とSinatra(Ruby)どっちがいいかね〜みたいな感じだったので、その判断するために実装読んでもらっていたりもしました。TSでいきましょうとなって、TSでいくことに。

最初のボトルネック探し

また、alpを使ってボトルネックを探すのも、 id:karur4n がやってくれていました(11:18)。

f:id:shgam:20190907222211p:plain

2台構成にする

マニュアルを読むと、3台まで使って良いことがわかったので、ウェブ用とDB用に分けようということで、同じ手順でもう1台サーバーをたてました。この新しいサーバーのプライベートIPを環境変数MYSQL_HOSTにすればええやろと思ったのですが、ベンチを走らせたところ、どうも接続できていない(11:23)。

セキュリティグループを疑って、3306ポートを開放するルールを追加(11:41)。

しかしうまくいかない...。ここでVPCの仕組み自体の理解が浅いために、あれ、プライベートIPの指定じゃだめ?なんか追加で設定必要?などなどの寄り道をします。寄り道をしたあげく、ping叩いてプライベートIPでちゃんと通信できとるやんというのに気付き、疑念が晴れる(12:01)。

通信はできるけどmysqlクライアントからの接続だけできんぞという問題の切り分けがようやくできたので、mysqlの設定を疑い、無事、MySQLbind-address オプションが127.0.0.1になっていたのを外すことができました(12:55)。

N+1潰し

僕が2台構成の設定をしている間に、id:karur4n/users/transactions.json のN+1潰しにあたりました。

ベンチ実行 -> failed.

2台構成、N+1潰しが入ったところでベンチを実行。failしました(12:57)。修正に時間かかりそうだったので、一旦ふたりとも昼飯を食うべく休憩に入ります。1時間くらい昼休憩にしてカレーを食べました(14:00)。

ISUCONサーバー環境git設定

そこまでソースの変更をscpでアップロードしてしていたのを、サーバー環境で git pull できるようにするべく、設定をしました。サーバー環境で ssh-keygenしたやつをGithubの設定画面で設定(14:06)。

git clone したけど、publicディレクトリがちゃんとコピーできてなかったりなんやかんやで時間がたっていたが、そんなこんなの間にfailedの修正もid:karur4n が終わらせてくれていました(14:50)。

ベンチ実行 -> 1210

ベンチがパスして、スコア5000だ!!!!やった!!!と喜んでいたら、見ていたのはJob idの方で、実際は1210イスコインでした(14:55ごろ)。

1台構成にしてベンチ実行 -> 900くらい

2台構成にした意味あるんか?という疑問があったので、環境変数変えてlocalhostMySQLにつなぐように戻して再度ベンチ実行。900くらいになったので、よしじゃあ2台にしとこうというやりとりをしました(14:55ごろ)。

ts-node -> nodeへの変更

ExecStart = /home/isucon/local/node/bin/node /home/isucon/isucari/webapp/nodejs/node_modules/.bin/ts-node index.ts になっていたのを、ExecStart = /home/isucon/local/node/bin/node /home/isucon/isucari/webapp/nodejs/node_modules/.bin/ts-node index.js ビルドしたやつを実行するように変更(14:58ごろ)。TypeScriptだとビルドあるからということでそうなったんですが、よく考えるとts-nodeも結局ビルドしてからプロセス立ち上げるのでこれはやらなくてもよかった...?

campaign値の変更

今回のお題はIsucariという架空の椅子専門フリマサイトであり、以下のような仕様になっていました。

POST /initialize のレスポンスにて、イスコイン還元キャンペーンの「還元率の設定」を返すことができます。この還元率によりユーザが増減します。 有効な値は 0 以上 4 以下の整数で 0 の場合はキャンペーン機能が無効になります。

ここまで(スコアだと1210)ではこれをずっと0にしたままだったので、値を変えてベンチを走らせた結果、以下のようになりました(15:16)。

f:id:shgam:20190907233535p:plain

外部サービスのURL取得にDBから引っ張るのやめようとする

アプリケーションから外部APIを叩いている箇所があり、そのAPIのURLがDBに入っていて、それを毎回SQLで取得していました。テーブルには外部APIのURLがこのように保存されていました。

mysql> select * from configs \G;
*************************** 1. row ***************************
name: payment_service_url
 val: https://payment26.isucon9q.catatsuy.org
*************************** 2. row ***************************
name: shipment_service_url
 val: https://shipment26.isucon9q.catatsuy.org
2 rows in set (0.01 sec)

じゃあこれを文字列としてソースコードに書いちゃっていいんじゃないか、と思ってそうしたものの、ベンチがfailするようになってしまいました(15:26)。

GET /users/transactions.json: got response status code 500; expected 200,
POST /buy: got response status code 500; expected 400 (item_id: 50002),
POST /buy: got response status code 500; expected 200 (item_id: 50001)

よくマニュアルを読むベンチマーク走行時にこの値は書き換わる とのこと。残念でした。

外部サービスのURLをキャッシュしようとする

せめてこの値をキャッシュしようとしました。lru-cache というパッケージを追加し、取得の関数でキャッシュを作り、以降はそれを見るように変更しました(16:30)。しかしスコアに改善は見られず...。

getItem

/itesm/:id.json に対応するgetItemという関数がだいぶクエリを発行していたので、これもJOINすればN+1潰せる系かと思い、手を出しました(16:53)。

が、結局悩んでいる間にその修正は間に合わなそうになります(17:21)。

API リクエストが直列になってるの並列にしようとする

id:karu4n の方では、/buy で行われているAPI リクエストを並列にしようとしていました(16:55)。こちらはコミットして変更を反映するものの、スコアに影響は出ませんでした(17:14)。

なぜかfailする

時間も時間なので、そろそろ大きい改善を諦めて調整に入ろうとします。が、ベンチを走らせるとfailedになっていまいました(17:24)。しかしfailedにならず普通にpassするときもあり、よくわからんなとなります。外部サービスのURLをキャッシュするやつがなにかよくない...?と推測してその変更を外して走らせたりしたところ、安定してpassするようになったので外して提出しようと思いますが、最後の方でキャッシュをsetする位置を変えた(/initializeが叩かれたときにキャッシュするようにした)やつで試したところ、passするようだったのでその変更入れました。

ウェブサーバーのmysqlを停止

なんかもう大きなことできないなと思ってつつtopを眺めていると、ときどきmysqldが上に上がってくることがあって、DBサーバーでもないのにこいついらんやろと思ったので、おもむろにウェブサーバーの方で sudo /etc/init.d/mysql stopしときました(18:00ごろ)。

nginxいじる

最後の最後でもう時間もないですが、ぺろっと設定入れて改善するものがあれば入れたいという感じで、 id:karur4n がNginxからgzip圧縮して返すような設定を入れてくれていました。スコアが伸びなかったのでもとに戻してフィニッシュ(18:10)。

感想など

以上は、当日終わったあとにやったことを振り返ってまとめただけのものなので、僕らの勘違いが含まれている可能性もあります。後日講評と解説が公開されたら、それを読みながら再度振り返りをしたいと思います。

こうやってやっていたことを振り返ると、1つ1つ検証せず複数修正を入れて一気にベンチ走らせてスコア伸びたり落ちたりして一喜一憂という場面もあったので、相変わらず雑だったなという反省もあります。

また個人的には、ソースコードを開けば明らかに無駄に発生してそうなDBへの問い合わせは見つかるんですが、それをスピーディに直してベンチ走らせてスコア伸ばすということができておらず、すごい純粋に力不足だったな〜と感じました。そこらへん毎年 id:karur4n のほうができているので、足を引っ張ってしまって申し訳ない。

一方で、一昨年、昨年も出てるので慣れてきたのもあり、やろうとしていたことはそんなに悪くなかったような気がします。マニュアル読んでまずサーバー環境を2台に増やすとか、修正をちゃんとgitで管理/反映するとか、alpでやばそうなところを見つけてそこを直すとか、failしたらログを見て原因を見つける、というようなことはできていたので、そこらへんは例年に比べてバタバタ感が減っていて良かったと思いました。

なにより楽しかったので、また来年も参加してみようと思います。一緒に出てくれた id:karur4n と運営の方々に感謝です。 お疲れさまでした 🍣

デスクトップPWA楽しい

趣味で https://github.com/gaaamii/nekobito というMarkdownエディタをElmで書いているんだけど、久しぶりに開いたら、Chromeアプリとしてインストールできるようになっていた。

f:id:shgam:20190827003521p:plain

ダウンロードされたものを見ると、 app_mode_loader という謎ファイルができているようだ。あとでこれどういうことなのか見てみたい。

どういう仕組みなのかよくわかっていないけどたぶん、以前PWAお試しようと思って雑に置いておいたmanifest.jsonのおかげのような気がする。

なにはともあれ、Elmで書いたものが、ネイティブアプリのように起動できて使える...!!楽しい!!!

趣味開発やっていきたさが高まった。

(追記)デスクトップPWAというらしい。楽しい。

「Elm Meetup in Summer」参加した

書くのが一日遅れましたが、Elm Meetup in Summer - connpassに参加してきました! 楽しかった〜!昨日ちゃんと感想書こうと思ったけど寝てしまったので、いまさらですが箇条書きで書いておきます。

感想

  • 人がたくさんいてElmの勢いを感じた。
  • Elm in Actionという良さそうな本を知ることができてよかった。
  • Fringe81の泉さんの個人開発リポジトリを知れてよかった。Firebaseは自分も趣味開発で使いたいので参考になる。
  • モジュール分割はみんな悩んでそう。
    • モジュール分割の方法が、これだ!っていうのがわかれば大きなアプリでも安心して作れそう。
  • プロダクトで採用してるという人が思ったよりいたけど、Elmはまだメジャーバージョンが0なのに採用できるのけっこう勇気あると思う。どんと来い、breaking change という感じなんだろうか(自分も0.18のときに採用したので人のことを言えない)。
  • Elm普段から使ってる人は割と普通に便利に使ってて、Elm大好き!というよりは淡々とやっていってる感じがあってよかった。
  • jinjorさんのスライドを見ればElmの歴史がわかるので時々見返したい。

仕事のやりがいとは何か?をもう一度観てみた


ずいぶん昔に、まだ働き始める前にこの動画を見た時は、ふーんそうなんだとしか思わなかった。そら褒められた方が仕事楽しくなるよねと。

働き始めてしばらく経ち、動画を見返してみた。動画で強調されているように、大げさなリアクションではなくちょっとしフィードバックだけでもモチベーションに影響があるということが、実感としてわかるようになっていた。

働き出す前に見たときよりも、動画内で語られているちょっとしたフィードバックの大事さが腑に落ちる感じがあって面白かった。

千鳥の「相席食堂」を観始めた

千鳥の「相席食堂」を観始めた。 芸能人が田舎にロケ行って、その土地の人と相席をさせてもらうという番組。 ロケの内容がひたすら雑で、それに対する千鳥のツッコミでめちゃくちゃ笑える。

Amazonプライムビデオのレビューには185件レビューがついていて評価が4.7と高評価ですごい。

「SCRUM BOOT CAMP THE BOOK」を読んだ感想

読んだ。

「基礎編」でスクラムの全体像を説明したあと、「実践編」では漫画でお話が進む。主人公の「ボク」くんがスクラムいいな〜ってぼやいてたら部長にスクラムマスターに任命されて奮闘する感じのストーリーになってる。キャラ設定もだいぶ細かくて面白かった。バッチくんが「バッチ得意です!!!!」と宣言したときは感動ものだった。

個人的には、当初スクラムに対して「ルールがっちり決められて堅苦しい進め方をするやつ」みたいなイメージがあったのだけど、これを読んでだいぶ希望が持てた1。特に現在の自分は実際の仕事でけっこうな失敗2をしたばかりなので、興味深く読めた。

この本に書かれていることを実践するのは面倒だし、やることが増えるように感じる。まずチームをスクラムにするというところから大変だ。

しかし、雑に見積もってえいやで始めて、完璧なソフトウェアが出来上がったりしないことはすでに経験した。

プログラムを書くときに、デザインパターンを利用すれば、「これはDecoratorです」みたいなことが言える。多少のぶれはあっても、自分の新しいアイデアを採用するよりは、認識は揃いやすくなる。それと同じように、スクラムも、チームの中で共通認識として持てることが強いのかなと思った。「どれくらいタスクを消化できるかわからない」となったら、まずベロシティを測るべきだし、「この仕様は誰に聞けばいいのかわからない」となったら、それはプロダクトオーナーに聞くべきだし、「なんかビルド遅いから直したいけどいつやればいいかわからない」となったら、プロダクトバックログに追加して優先度を上げる相談をすればいい。

スクラムのようなものがないとどうなるか?どれくらいタスクをこなせるかもわからず無理なスケジュールを提示してしまうかもしれないし、細かい仕様は開発側でよしなにやってくれということになるかもしれないし、ビルドの改善は残業してやるしかないかもしれない。その問題が、問題と認識されることすらないまま、誰かの頑張りでどうにかするしかないかもしれない。ある瞬間はそれでもいいのかもしれないけど、チームの中でその認識がずれていると、不満が募って、チームのメンバーが退職してしまうかもしれない。

スクラム銀の弾丸どころか、実践が難しくて面倒で堅苦しいものかもしれないけど、複数の人間が共通認識を持つには同じ言葉を使って何度も何度も話し合うことが必要で、スクラムがそのフレームワークなんじゃないかと、そんなことを考えた3


  1. 実際はこの本読む前に id:hiroyuki-hanai さんによる研修も受けさせてもらっていて、それも理解の助けになった。

  2. 幸いリリースはなんとかできたけど、スケジュールは破綻してしまっていた

  3. そんなことはこの本のどこにも書いていないけど

入社して3年経ってた

3年目、ずっとダメダメでしんどかった。今後もエンジニアとしてやっていけるのか不安でしかない。

諸々落ち着いたら休みを取ってゆっくり振り返りたい。

追記

どうぞって...誰に向けてだよ

追記その2

ずいぶんネガティブな感じに書いてしまったけど、休んだら回復してきた。またがんばるぞ。

「CODE COMPLETE」を読んだ感想を書きたい

会社の先輩から勧められて借りてきた。上巻から読んでる。

コンストラクション

この本ではソフトウェアづくりのことをコンストラクションと呼んでる。コンストラクションの範囲のことは語るけどその外のことはこの本の範疇外やでということらしい。コンストラクションの範囲は、ひどいソフトウェア開発だろうがうまいソフトウェア開発だろうが絶対通るような、作る工程のことを指してるっぽい。設計とかは入るけど、顧客折衝とかは入らない感じっぽい。

リーダブルコードだと基本的にコーディングのことばかりだけど、この本はもう少し広い範囲を対象にしてそうというのがわかる。

第5章

第5章は設計の話。設計っていうのはヒューリスティック(発見的)なものであり、決定論的なものじゃないということが書かれている。正解があって、一発で、はいこれねと正解が出せるものではなさそうなのがわかる。

第6章

具体的な話になってきた。ADT(Abstract Data Types)の話。 プログラムのデータ型というとまずstringとかintとかそういうプリミティブなものがあるけど、それをそのまま使うんじゃなくて、現実世界のものを抽象化して表現すると良いぞ、みたいな話。読み進めながら、なるほどオブジェクト指向のクラスの話かと思ったけど、それよりも土台の話らしい。

ADTはクラスの概念の土台となる。クラスをサポートするプログラミング言語では、ADTをそれぞれ専用のクラスとして実装することができる。通常、クラスには他にも継承やポリモーフィズムという概念がある。クラスは「ADT + 継承およびポリモーフィズム」として考えることもできる。

第7章

ルーチンの話。関数(値を返すルーチン)とプロシージャ(値を返さないルーチン)の使い分けなど。C++のマクロの話はあんま関係ないな〜と思って読み飛ばしてしまった。

自分は普段ここで書かれているルーチンの種類を特に区別せず全部関数って呼んでたのだけど、値を返すかどうかによってそうやって言い分けるものなんだ〜というのを学んだりした。

第8章

防御的プログラミングの話。garbage in garbage out (ゴミを入れてゴミを出す)ではだめで、エラーメッセージを出したり、そもそもゴミを入れさせないようにしたりと、ゴミに対処するべきという話。

8.3.1 では堅牢性と正当性という言葉が出てくる。

正当性とは、不正確な結果を決して返さないことを意味する。不正確な結果を返すくらいなら、何も返さない方がましである。堅牢性とは、ソフトウェアの実行を継続できるように手を尽くすことである。

どんなアプリケーションのどんな機能かによって、正当性を優先するか堅牢性を優先するかが変わってくる。正当性を優先すれば、誤ったものがきたときにエラーメッセージを出して処理を中断とかにするだろうし、堅牢性を重視するなら、そうはせずに近い値やデフォルト値みたいなものを変わりに入れて処理を続行したりする。

また、8.4では例外についても触れられている。安易に例外使わないようにしたほうがいいよというスタンスで、こんなことが書かれている。

例外は、予想外の状況に対処する強力な手段と、コードの複雑さの増大とのトレードオフを表す。たとえば、あるルーチンを呼び出すためには、呼び出し元のコードはどこでどの例外がスローされるのかを知らなければならない。したがって、例外はカプセル化を弱め、これによりコードの複雑さが増し、「ソフトウェアの鉄則:複雑さへの対応」にマイナスに働く。

第9章

擬似コードによるプログラミングの話。

第10章

変数の話。なるべく宣言した近くで使おうねという話など。

第11章

第11章は変数の名前の話。その変数が持つ意味を考えて、あとで読んだときに推理しないでもぱっとわかる変数名つけようねという話。

第12章

第12章は基本的なデータ型。浮動小数点数の話とか。


書き途中です。

TypeScriptのkeyofの使いどころ

読者登録したブログ記事をだらだらと巡回していて、こちらの記事を拝見しました。淡々と学んだことをブログに書いていてすごいなと思っていつも読ませてもらっています。

yurufuwa-tech.hatenablog.com

特定のオブジェクトのkeyの値しか許容しない、みたいなパターンの時に使えるって話なんだとは思うのだがこれがどこで使えるのか…というのはちょい謎。。。

TypeScriptは覚えることが多くて自分もあまり自信がないのですが、keyofについてはちょうど最近便利だな〜と感じたことがあったので、傲慢にも勝手にアンサー記事みたいなのを書こうと思いました(間違ってること言ってたらご指摘ください)。釈迦に説法だったら生温かい目で見てください。

Formikのフィールド名に使える

具体的すぎ感ありますが、keyofはFormikのフィールド名を表現するのに使えました。

FormikというのはReactでフォームを扱う際に便利コンポーネントを提供してくれるライブラリです。Formikを利用したフォームは、通常のHTMLフォームと同様、フォームがあって、そのなかにフィールドがあって、それぞれのフィールドにはname属性がついている、みたいな形になります。

const MyInnerForm = (props: Props) => {
  return (
    <Form>
      <Field name="hoge" />
    </Form>
  )
}

Formikを使うと、↑こんな感じでフォーム要素を記述して、 ↓withFormikというHOCで必要なコールバックなどを流し込んだコンポーネントを作れます。

const handleSubmit = (values: Values) => { /* ...省略 */ }
const validate = (values: Values, props: Props)  => { /*...省略 */ }
const MyForm = withFormik({ hanldeSubmit, validate, /* ほかにもいろいろ入れるけど省略 */,  })(MyInnerForm)

Formikは、ユーザーがそのフィールドを触ったかどうかとか、validateで判定したエラーの結果なんかをFormikのpropsとして持っています。

なので、エラーがあるときはこんな形で参照できます。

interface Values {
  hoge: string;
  fuga: string;
  foo: string;
}

const MyInnerForm = (props: Props & FormikProps<Values>) => {
  console.info(props.touched.hoge)
  console.info(props.errors.hoge)

  return (
    <Form>
      <Field name="hoge" />
    </Form>
  )
}

で、場合によってはここで以下のような hasError みたいな関数を定義したくなります(なりました)。 使いやすい、イケてるウェッブサービスをつくりたいので、hasErrorだったときはその場でフィールドを赤くしたりしたくなるのです。

const hasError = (props: Props & FormikProps<Values>): boolean => {
  return props.touched.hoge && props.errors.hoge
}

いいんじゃないでしょうか。しかしこれが複数のフィールドになると面倒です。

const hasErrorOnHoge = (props: Props & FormikProps<Values>): boolean => {
  return props.touched.hoge && props.errors.hoge
}
const hasErrorOnFuga = (props: Props & FormikProps<Values>): boolean => {
  return props.touched.fuga && props.errors.fuga
}
const hasErrorOnFoo = (props: Props & FormikProps<Values>): boolean => {
  return props.touched.foo && props.errors.foo
}

関数1つにしたいですね。フィールド名を渡すようにします。

const hasError = (props: Props & FormikProps<Values>, fieldName: string): boolean => {
  return props.touched[fieldName] && props.errors[fieldName]
}

できた〜。イケてる〜。

となるんですが、問題は、fieldNameの許容する値が広いことです。

hasError(props, 'hogeee')

こうしても、型のエラーにはなりません。stringなのだからそりゃそうです。

ここでようやく、keyof の出番です。これでどうでしょう。

const hasError = (props: Props & FormikProps<Values>, fieldName: keyof Values): boolean => {
  return props.touched[fieldName] && props.errors[fieldName]
}
hasError(props, 'hoge')
hasError(props, 'hogeee') // Argument of type '"hogeee"' is not assignable to parameter of type "hoge" | "fuga" | "foo"

打ち間違えてhogeeeにしたらちゃんと怒られるようになりました。めでたし。

まとめ

TypeScriptほんと難しくてあれやこれや覚えないとな〜という感じで、自分で型書いているときはあまり難しいことはできていないのですが、keyofはわりと出番がありそうだなと思ってます。

関連

「儲かる会社、つぶれる会社の法則」を読んだ感想

面白かった。

こんな会社はだめ、気をつけろ、みたいな例がたくさん書かれていた。 ああそらだめそうですなと思うのと同時に、 いま自分が勤めている会社は割といい会社なんじゃないかとも思えた。

「幸せな選択、不幸な選択――行動科学で最高の人生をデザインする」を読んだ感想

読んだ。面白かった。

まず、幸福度を測るの難しそうだなという感想を持った。とはいえ自分は研究者としてではなく読み物としてだらだら読んでるだけなので、調査方法の詳細はへえそうなのというレベルで読み流した。

第4章の内容はけっこう好きだ。目標とかやりがいといったものについて考える上で参考になる。このへんは気に入った。

もちろん 、目標達成や真正性といった他の考慮材料もたしかに重要だ 。ただし 、これらが重要なのはそれに 「道具的価値 (手段的価値 ) 」があるからで 、つまり 、幸福度を高める意味においてのみ重要なのだ 。目標達成や真正性は概して幸福度を高めるが 、私たちがその奴隷になってはならない 。

何を幸せかと考える上で、仕事なり競技なりでトップを目指すような、いわゆる意識高い系の考え方と、そんなことより幸せに過ごしたいみたいなゆるめの考え方があると思うんだけど、そのどちらにとっても、この考え方は役立つと思う。

日高屋実践入門

昨年度1年の支出なんかを振り返っていて、ずいぶん贅沢してしまったなと反省した。 今年度からは、一人で行く昼飯とかで無意識に贅沢しないようにしようと思い、日高屋によく行くようになった。

なぜ日高屋なのか

量を少なめにしても、味が濃いので満足できる。

なにを頼んでいるのか

  • 半チャーハン:¥260
  • 餃子(3個):¥130

昼飯が¥390におさまる。すごい。

すこし変化を入れたいときはイワシフライ(¥230)を頼んでいるが、それでも¥490である。すごい。

健康

健康にはあまり良いとは言えない。ただ、朝夕の飯さえちゃんとすれば絶望的に健康に悪いというわけでもないと思う。

まとめ

日高屋は好きだが、さらにコスパの良い外食があるような気もしている。 これがいいぞなどあれば教えてください。