1週間前ぐらいまで毎日いろいろと資料を読んでコード書きまくったりしていたけど、例によって急激にやる気が失われてほとんど何もしていない。

  1. ハードウェア周りのコード書くやる気が急激に失われる
  2. サイトの最適化まわりにやる気を出す
  3. 全てのやる気が失われる (だいたいやったから)

という感じで進行した。やる気があるときは「ゲームとかやってる暇全くないし、ゲームとか完全に無駄な時間を過ごすだけ。酒飲んで頭回らない時間をつくるのも無駄」みたいな意識の高さがあるが、そうでないときはとにかく頭を一切使いたくないという感じになる。

ハードウェアまわりは明確にやりたいことはあるけど、途中でやる気がなくなったのでそれ以来全くすすめていない。

ディアブロ3を久しぶりにちょっとやってるけど、目が疲れる。

どっか散歩したいけれど、忌中なので神社には行けない。つつじヶ丘あたりを地図なしで歩いたりしてみたりしたけど気分は晴れず。ストレスは溜まるがやはり解消方法がない。

二重の意味で(出勤も保育園も)はぁ嫌だなあと思いながら保育園に行ったら、保育園が開いていなかった。意味がわからなかったけどとにかく開いていなくて、どうしようもないので帰ってきた。

自分はこういう予期していないトラブルに滅法弱くて、普段に数倍に嫌な気持ちが増幅されるんだけど、そうはいっても現実として「誰が子供の面倒見るの?」という問題を解決しないといけないので、会社を休んだ。

結局なんだったかといえば保育園は諸事情 (特に複雑な事情ではなくて、年間行事表には書いてあった) によって休みだった。昨年は土曜日にあたっていたようで意識しなかったし、休みになるという認識がなかった 。そして今年は直前に子供の発熱があったせいか特別休みの予告がなかったので、誰も休みを認識してなかった。

来年同じことがないようにカレンダーにリピートを入れた。

別に保育園が悪いわけではないけど、ほんと保育園関係は嫌な気持ちになることが多い。暦通りじゃない休みがこんなにアナウンスされないものなのか。

準備して、子供が寝てるとこを起こして(朝食をとったあと、たまに寝ることがある)、だっこして、門まで連れていって、開いてない。平日だから当然出勤するつもりでいる。出勤する人の流れが見えるから平日なのは間違いない。寝ボケていて時間を間違えたのか。開いてないから帰るしかない。おやつやら昼食やらをどうするか考えないといけない。夕方までの間のすごしかたを考えないといけない。スワップアウトされてるもろもろを頭の中のワーキングメモリにロードしなおさないといけない。

自分のイライラするシチュエーションを考えみると「ワーキングメモリに大きなデータをロードする必要がある」ことと「予期してない」ことが大きく関係しているような気がしている。

自分は頭の中のワーキングメモリに、何らかの仕事(プログラム全体の設計とか)をロードするのにとても時間がかかる。このロードされたイメージが何らかの出来事(外部割込み由来)で無駄になるときにイライラする。

生活全般で

保育園なんかで、1円にもならない仕事をやらされたりする。全体としてたいしたことじゃなくても、その仕事をロードするたびに、趣味にしろ仕事にしろ、頭の中のイメージにリセットがかかる。そして再度元の仕事をロードする必要がある。

予期できることもあるけど、そもそもストレスかかる仕事が多いのでコンテキストスイッチが発生するだけで不愉快。

仕事において

しばしばあるのが、些細な仕様変更・追加で、これにより根本的に直す必要がありそうだというケース。

そういうことが起こらないように、将来的にどんな仕様の追加がありそうかということをエスパーして、予期されるそれらへの変更耐性を設計に組み込んだりするする。そういうのも含めて、ピースをうまく噛み合せて全体的に「これでうまくいきそうだな」というイメージを構築するまで、まずとても時間がかかる。

しかしいろいろ考えたとしても、考慮外の仕様が出現したりする。この「考慮外の追加仕様」はイライラする。というのも

  1. 頭の中にロードされたイメージの中にどのように割り込ませるかを考えたうえで
  2. 場合によって全体のイメージを再構築する必要がある

それほど複雑ではない場合は部分的な再構築ですむのでそれほど最悪な気分にはならないが、全体を再構築する必要がありそうな場合は、頭の中を全部一旦空にするので再度全体の設計をロードしなおすまで時間がかかる。その間ずっとイライラしている。

どうすればいいのか

頭の中にロードする時間自体を短縮するのは難しそうなので、イメージのサイズを小さくするとか、予期できそうなことは予期しておくしかない? しかし予期できそうなことを予期しておくというのをやるとイメージのサイズが大きくなる。

そもそも何故頭の中のイメージが初期化されることでイライラするのか。

今までは Cache::FileCache によるファイルシステムキャッシュにしていたけど、いくつか問題があって SQLite にかえた

ファイルシステムキャッシュで困っていたこと

  • なんか遅い
  • キャッシュ無効化の処理のためにキャッシュを生成元とキャッシュのキーをキャッシュ内に格納したいが、アトミックにやるうまい方法がなかった

SQLite 選択

Redis とかを立てるのが機能的には便利そうだけど、リソース的にあんまりサーバプロセスを増やしたくはないので、SQLite とした。

CREATE TABLE cache (
	`cache_key` TEXT NOT NULL PRIMARY KEY,
	`content` BLOB NOT NULL
);

CREATE TABLE cache_relation (
	`id` INTEGER PRIMARY KEY,
	`cache_key` TEXT NOT NULL,
	`source_id` TEXT NOT NULL
);
CREATE INDEX cache_relation_index_cache_key ON cache_relation (`cache_key`);
CREATE INDEX cache_relation_index_source_id ON cache_relation (`source_id`);

CREATE TRIGGER on_cache_deleted AFTER DELETE ON cache BEGIN
	DELETE FROM cache_relation WHERE cache_key = old.cache_key;
END;

CREATE TRIGGER on_cache_related_deleted AFTER DELETE ON cache_relation BEGIN
	DELETE FROM cache WHERE cache_key = old.cache_key;
END;

こんな感じでキャッシュ本体 (cacheテーブル)と、そのキャッシュを生成するのに使ったもののid(文字列)のリスト(cache_relationテーブル)を持って、お互いにトリガーで消しあうようにしておく。

こうしておくと、通常のキャッシュキーによる追加・削除だけではなくて、生成元が更新された時に関連するキャッシュをまとめて消せる。


なお、キャッシュ用のDBファイルは元データと分けた。というのも、元データのDBは毎日バックアップとしてGmailに送りつけているので、キャッシュを含めたくなかったから。

  1. トップ
  2. tech
  3. ブログのキャッシュバックエンドの変更

ストッケ トリップトラップ ウォールナットブラウン -

5.0 / 5.0

2歳になって自分でできることが増えてきたり、「みいつけた」で椅子を見ているせいか、椅子が好きになってきたみたいなので、本人だけで座れる椅子を買いました。

ちょっと高いけど、10年ぐらい使えそうなので、そう考えればそこまで高くないかなと思ったのと、こういう大物は買ったり捨てたりするのが面倒なので、ストッケ・トリップトラップというのを買ってみました。色はウォールナットブラウン。

届いて組み立ててる最中から子供はだいぶテンションが高くて、六角レンチでボルトを締めようとしたり(むしろ緩めてたけど)していて面白かった。

ちゃんと独りで座ることもできるし、安定しているので良さそう。ただ子供は「新しいものは様子を見ながら使う」というのがわかってないので、ドンドコドンドコやっててハラハラする。

ところで最初組み立て図を見ても、座面とかにはボルトがなくて、どうやって固定されるかがよくわからなかった。組み立ててる途中でようやくわかったが、これの座面は左右の支えの窪みに入れた状態で、他の部分のボルトを締めていくことで挟みこんで固定されるようになっている。面で固定されるから直接ボルトで固定するよりもいいのかな。増し締め忘れないようにしないといけなそう。

1週間ぐらい既に使っているけど、表面塗装のせいか汚れても思いのほか拭き取りやすい。あと、倒れないように足の底部に滑る部品(滑り止めではない)がついていて、面白かった。子供がテーブルを蹴って後ろに倒れないように、椅子自体を滑らせるという形のセーフティになっている。ただ、性質上、床面がフローリングでないと機能しない。うちはフローリングにマットを敷いているので残念ながら意味ない。

今まで使っていたもの

カトージ キャンピングホルダー5点式 NewYorkBaby 58808 -

5.0 / 5.0

これを今まで使っていた。まだ使えそうだけど、自分で座ることができなくてちょっと可哀そうになってきた。それに毎回大人が持ちあげる必要があるが、子供も既にそこそこ重い(12kgぐらい)ので結構つらい。

汚れたら風呂場でザーーーと洗って干すという雑な運用をしていたけど、半日ぐらいですぐ乾いてくれるので良かった。値段も安かったし、十分に使えたと思う。

ottostyle.jp 子供用絵本ラック 木目調 (キッズブックシェルフ) 幅64cm×奥行き29cm×高さ75cm 【収納に便利な仕切り棚付き】 (ダークブラウン) -

4.0 / 5.0

今までの本棚に不満があったのでこれを買ってみました。本体がかなり重くてしっかりしてます。

子供は特に何の反応もなく本棚と認識して使っていて、あっけない感じだった。絵本渡して「ないないして」というとちゃんと入れてくれる。よかった。

今まで使っていたもの

森井紙器 すまいるキッズ 絵本ラック レッド -

2.0 / 5.0

買ったときはあんまり本もないからいいかと思ったけど、急激に増えてしまって納まりきらなくなった。

この商品はダンボールでできていて軽いんだけど、おかげで子供が中の本を全部出して、本棚を振り回して遊んでることが多々あった。さすがに最近はそんなことしないけど、軽くて小さいというのも考えものだなあと思った。あとダンボールなので、乱暴に扱うとかなりヨレてしまう。

結果的にはこれを買ったのは失敗だった。最初からそこそこのものを買ったほうが良かった (どうせ必要だから)。


岡元太郎の母の塔です。子供がこわいこわい言う。

  1. トップ
  2. photo


トラックバックを実装しました

といってもサイト内のエントリ間の言及を表示するだけです。いわゆる古代のオープンなトラックバックではありません。

古いエントリに新しいエントリへのリンクがないのは不便だなと思い、とりあえずさくっと LIKE 検索で機能を足してみていたのですが、良さそうなので、ちゃんとリレーションテーブルをこしらえたりしました。

しかし、全体的にページキャッシュ機能を入れているので、単に機能を足すだけでも考えることが増えてしまいます。イメージではトラックバックが送られたページに更新がかかるというだけですが…

キャッシュ処理も見直して、全体的にテストも書いたりしました。キャッシュが入ると複雑性が増す分、独り人力QAには限界があるので、みっちりテストを書くほうがかえって省力化します。

  1. トップ
  2. tech
  3. トラックバックを実装しました

ps とかで表示されるプロセス名をわかりやすくしたいという話です。

Starman だと $0 に適当な値を入れてくれて、master なのか worker なのかとか、どの psgi が動いているかとかがわかるのですが、Starlet ではそういうことをやってくれないので、1つのサーバで複数インスタンス動かしていると、plackup のプロセスだらけで、どれがどれだかわからなくなります。

app.psgi で $0 で設定すれば良いかと思ったが

app.psgi で $0 設定したらとりあえずいいだろ、と思ってやってみましたが、どうも上手くいきません。app.psgi 内で $0 を warn してみるとそもそも plackup になってもいません。

調べてみると、Plack::Util::load_psgi で使っている _load_sandbox 内で local $0 をしてから app.psgi を do しており、ロード後に元のプロセス名に戻ってしまうようです。

ということで、単純に app.psgi で $0 を書きかえても plackup を使っている場合書きかわりません

Starlet 用のハックを入れる

なんかうまい方法がなさそうなので以下のようなクソハックを app.psgi に入れました。

{
    ### set process name for Starlet

    use Parallel::Prefork;
    my $name = $0; 
    my $orig_new = \&Parallel::Prefork::new;
    no warnings 'redefine';
    *Parallel::Prefork::new = sub {
        my ($class, $opts) = @_; 
        $opts->{before_fork} = sub {
            $0 = "$name (worker)"
        };  
        $opts->{after_fork} = sub {
            $0 = "$name (master)"
        };  
        $orig_new->($class, $opts);
    };  
};

Starlet は Parallel::Prefork を使っているので、前もって use して new を書きかえて before_fork / after_fork を設定します。

before_fork / after_fork は Parallel::Prefork の機能でセットできるフックポイントですfork 前に worker プロセス用の $0 を設定して、fork 後に $0 を master 用の値に変えています。なのでタイミングによっては master プロセスも一瞬 worker 表示になります。

app.psgi ロード時のコンテキストでは $0 には .psgi のファイル名が入っています。これは上記の Plack::Util::_load_sandbox で設定されたものです。ということで、ロード時のコンテキストの$0を保存しておいて、後の $0 の設定に使っています。

app.psgi が Starlet 専用みたいになってキモいですが、Parallel::Prefork をロードする以上の害はとりあえずないのと、自分の場合は開発中に Standalone で動かす以外は Starlet を使うことにしているので問題ありません。

これで以下のようになりました。

cho45     4699  0.0  0.5 185432  5332 ?        S    May11   0:00 /home/cho45/project/COQSO/script/app.psgi (master)
cho45     4700  0.0  2.4 296604 25168 ?        S    May11   0:09 /home/cho45/project/COQSO/script/app.psgi (worker)
cho45     4701  0.0  2.5 296820 26468 ?        S    May11   0:11 /home/cho45/project/COQSO/script/app.psgi (worker)
cho45     4702  0.0  1.8 302304 19136 ?        S    May11   0:11 /home/cho45/project/COQSO/script/app.psgi (worker)
  1. トップ
  2. tech
  3. Starlet でプロセス名をわかりやすくしたい

  • RTT (ping) が約30ms〜50msぐらいの環境
  • * がついているのは http2 の push
  • HTML 内で直接参照しているリソースについてもダウンロードしている (nghttp の --get-assets オプション)

今までのページャは良くある ?page=2 みたいな形式でした。これは内部的には offset / limit を使う SQL になります。

変更したページャは /.page/20160509/3 という形式です。20160509 が表示するページ番号、3 がそのページに表示する最大の数です。ページ番号はトップページからのページの場合、日付になっており、内部的には日付ベースの where 句になります。3 は limit です。

これらによって、サーバ側のエントリ表示数のデフォルト設定によらず、URLによって決まる内容が生成されます。よって

  • キャッシュ無効化の負荷が減る
    • 古い形式だと ?page=xxx なキャッシュは1つエントリが増えるたびに全て無効にしないといけません
  • 古いページを表示したときの負荷が減る
    • offset / limit はそのページに至るまでの全てのエントリをソートして辿る必要があるので古いページをページ指定すると不穏な空気が流れます
  • 検索流入したとき見たいコンテンツがないことが防げる

余談:この日記の構造

このサイトは「日記」なので、日付単位でエントリがまとめられています。「ブログ」の表示に慣れていると違和感があるかもしれませんが、以下のような違いがあります。

  • ブログは全エントリを通して新しい順に並ぶ
  • 日記は日付単位で新しい順に並び、日付内では古い順に並ぶ

ちょうど、紙に日記を書くときのように、1日の中では連続し、1日単位では独立しているというイメージです。

この挙動は正直初見ではわかりにくいと思うのですが、過去分は1日単位で上から下へ書かれる前提で複数のエントリを構成しているケースがあって、変えるに変えれません。

  1. トップ
  2. tech
  3. ページャの実装変更

子供のおもちゃのチョロQを見ていて不思議に思ったことがあって調べたので書いておきます。

遊びかたの先入観

チョロQみたいなプルバック式のミニカーは、後退するときにぜんまいを巻き上げることで、前進時に動力にするわけです。なので、くるま全体を床に押し付けたまま前後してもぜんまいは巻き上がらず (後退で巻き上がっても前進させた分巻き戻る)、走らないと思っていました。

自分の遊んだ記憶だと、後退するときだけ押し付けて、一旦持ちあげてもう一度後退させていた覚えがあるのです。

いつこの「プルバックの走らせかた」を学んだのか全く覚えていませんが、当然そういうものという認識でいました。そしてそもそも「後退時に巻いたぜんまいで動く」以上の仕組みを知りませんでした。

実際の挙動

子供は「プルバックの走らせかた」を知らず、適当に前後させたらなんかうごくことだけ学習しています。

子供が遊んでるときの挙動を見てるいると、ただ床に押し付けてゴリゴリと前後するだけでもぜんまいが巻き上がり、しかもどう見ても巻いた以上に長い時間回転することに気付きました。

前進時と後退時でギア比が変わる。

Wikipedia の「プルバック式」の項を見ると

ゼンマイを使うプルバック式動力では、後退の時のみギア比の大きい状態になるよう、内部の歯車が浮き上がるようになっており、ゼンマイの力で前進する時だけタイヤを高速回転させるような構造を持つものも多い。

と書いてあって、ギア比が前進と後退で変わるような仕掛けがあるようでした。

「プルバック ギア」とかで検索すると、もうすこし詳しいページも見つかります。構成するギアのうち2つのギアの軸は方向によってずれることで、いずれか一方が噛み合うように調整されているようです。

もしかすると常識なのかもしれないですが、こういう面白い仕掛けがしてあるとは考えていなかったので、びっくりしました。

結局ガルパンは5回ぐらい見てしまった。劇場版がみたい。

ボトルガムを買うと、だいたい2日ぐらいでなくなってしまって金がもったいないという感じになってしまう。

職場にいると無限にストレスが溜まってるので、手の届くところにガムとか食べものを置いておくと無限に食べてしまう。もったいないので、できるだけ買わないで水をガブ飲みしてるけど、口が寂しい。

そういえば、ものすごくイライラしているときは掃除すると多少気が紛れる (やる気になった場合だけだけど)。すこし前にイライラしたときは、ひたすらトイレ掃除をしていた。

掃除には良い点がいくつかあって

  • 満足するまで終わりがない
  • 頭を使わない (普段使っている回路を使わない)
  • 目に見える成果がある

一定の「満足感」は手に入ることがほぼ保証される。そんなものは他にあるか? ネトゲとかは瞬間的な満足感はあるが社会的には虚しい。掃除は同居家族がいるなら社会的な利益もある。

しかし、どういった種類のことでも「やらされている」と思った時点ですべての利点は失われる。他人のために働くということをすると、自分で満足するということがなくなる。成果評価の裁量を他人に奪われた時点で生きてる実感はなくなる。

少しでもストレスが解消されることがら

  • 散歩
  • 掃除
  • 睡眠
  • うざくないお笑い番組 (内村さまぁ〜ずみたいなやつ)
  • 悪役が出てこないアニメ
  • 高徳な主人公のアニメ

大きくストレスが溜まることがら

  • 路上喫煙 (避けようがない)
  • 仕事 (避けようがない)
  • 自分から見て道徳感のない人間の存在 (避けようがない)
  • 道徳観のない人間の発言 (はてブを見なければ、ほとんどエンカウントしない)
  • 他人の儲かった話
  • 自分が直接的にしろ間接的にしろ否定されるようなナニか全般

他人の道徳観に関するストレス (路上喫煙も同じ) は「こんなやつが社会でうまく生きてるのに、なぜおれは社会でこんなに否定されているのか」という感情に基いている。

Server::Starter は hot deploy 用の汎用スーパーデーモンで、Perl で書かれています。h2o の起動にも使われているのでみなさんおなじみでしょう!

Server::Starter がやってることは、Server::Starter 側で listen したソケットの fd を環境変数につっこんで子プロセスを起動というものです。子プロセス側では渡ってきた環境変数を読んで、fd について accept すれば良いことになります。

これを node.js でやるには以下のようにすれば良いようです。

//#!/usr/bin/env node
"use strict";

const http = require('http');

const server_starter_port = process.env['SERVER_STARTER_PORT'];
if (!server_starter_port) {
	console.log('SERVER_STARTER_PORT is not set');
	process.exit(1);
}
const fds = server_starter_port.split(/;/).map( (i) => i.split(/=/) );

const server = http.createServer(function (req, res) {
	res.writeHead(200, {'Content-Type': 'text/plain'});
	res.end('Hello World\n');
});

process.on('SIGTERM', () => {
	console.log('server exiting');
	server.close();
});

for (let fd of fds) {
	console.log('listen', fd);
	server.listen({ fd: +fd[1] });
}

起動はたとえば以下のように

start_server --port=5001  -- node server.js 

http.Server の listen() のドキュメントには fd を渡せるバージョンが書いてありませんが、net.Server の listen() がサポートしているので、同様に渡せるようです。

これだけで node.js のプロセスの hot deploy が簡単にできます。

余談:cluster

node.js には cluster というのがあります。これは node.js を複数プロセスで動かすための仕組みなんですが、これの woker プロセス側の listen() は master プロセスと ipc してうまいことやるみたいな感じのようです。

  1. トップ
  2. tech
  3. Server::Starter を node.js のサーバ起動に使う

このサーバはVPS 1台で動いていて、メモリは1GBしかありません。常時メモリ上限まで使いきっており、スワップファイルもそこそこあります。そういうわけで、できるだけメモリ消費をケチりたいのです。

稼働率の低い複数のサービスを1つのプロセスにまとめる

このサーバ上で動いているサービスがいくつかあるのですが、実際のところどれも殆どアクセスはありません。一番アクセスされていてこの日記システムぐらいです。

それらのサービスをそれぞれ別プロセスで起動させておくと、たいへん無駄なので、できるだけプロセス自体を同居させています。

psgi の vhost 化

Plack::Builder が提供している mount() を使うと、vhost を実現できます。

builder {
    mount 'http://lowreal.net' => do {
        my $guard = cwd_guard("lowreal.net/");
        Plack::Util::load_psgi("script/app.psgi")
    };
    mount 'http://env.lowreal.net' => do {
        my $guard = cwd_guard("env.lowreal.net/");
        Plack::Util::load_psgi("script/app.psgi")
    };
};

このようにすると、Host ヘッダに応じて、ディスパッチするアプリケーションを変えることができます。

これを利用して、複数の psgi アプリケーションを1つの psgi アプリケーションにまとめて起動しています。

ただし、起動時 (load_psgi時) にだけ cwd を設定しているため、cwd 依存のコードがあるとうまく動きません。

単にプロセス数も減らせますが、それぞれのアプリケーションで共通で使うモジュールがかなり多いので、それらを fork 前にロードして共有しておくことで、プライベートメモリ使用量を削減しようという意図もあります。

メモリリークや Copy on Write が発生したプロセスを捨てる

上記のまとめた psgi アプリケーションを start_server と plackup 経由で起動しています。

start_server 用には以下のような環境変数を設定しています

export KILL_OLD_DELAY=15
export ENABLE_AUTO_RESTART=1
export AUTO_RESTART_INTERVAL=3600

exec setuidgid cho45 \
        $PERL/bin/start_server --path=/tmp/backend.sock --port=5001 -- $PERL/bin/plackup -p 5001 -s Starlet\
        --max-workers=5 \
        --max-reqs-per-child=500 \
        -a backend.psgi

ENABLE_AUTO_RESTART によって、時間経過で自動的にプロセス全体を再起動しています。再起動間隔は AUTO_RESTART_INTERVAL に決まり、この場合1時間ごとになっています。

KILL_OLD_DELAY には新プロセスが起動して、リクエストが受けつけられるようになるまでにかかる時間を余裕をもって指定します。これを指定しないと、プロセス再起動がかかるたびに、モジュールなどのロードが終わるまでの数秒アクセス不能時間ができるので、特に ENABLE_AUTO_RESTART する場合は必須そうです。

これで、もしメモリリークしていても1時間以内に綺麗になることが保証されるとともに、fork した worker プロセスが働いて Copy on Write が起こってプライベートメモリ量が増えたとしても、1時間以内に fork しなおしになるため、共有メモリ量が一定より高い状態を保つことができます。

余談:プライベートメモリ使用量を観測する

Linux のプロセスが Copy on Write で共有しているメモリのサイズを調べる - naoyaのはてなダイアリー にある shared_memory_size.pl が便利でした。共有メモリの割合を表示してくれるのでわかりやすいです。

Starlet のプロセス名をわかりやすくしたかったのも、このスクリプト実行時に指定しやすくするためでした。

こんな感じで使っています。

shared-memory-size.pl `pgrep -f /srv/www/backend.psgi`
PID     RSS     SHARED
29583   55460   47792 (86%)
29584   55760   44688 (80%)
29585   65224   41396 (63%)
29586   56280   44704 (79%)
29587   55352   44660 (80%)
29588   58908   46748 (79%)

起動してすこし経ったあとの状態です。起動直後は90%以上ですが、プロセスが動くごとに少しずつ共有割合が減っていきます。自分の環境で、観測したうちだと60%ぐらいまで下がるようです。

ps -c で見るメモリ使用量には共有分が考慮されていないので、実際の物理メモリ使用量よりもかなり大きく出ます。仮に1プロセスあたり70MBぐらいメモリを消費していても、うち40MBぐらいが共有であることを考えると、worker プロセス1つあたり30MB、5プロセスで150MB程度の物理メモリ消費量になります。

  1. トップ
  2. tech
  3. 複数の psgi を1つのサーバでサービスするときにメモリをケチる

アクセスログを見ていたら Applebot なるものからのアクセスがあった。

https://support.apple.com/ja-jp/HT204683

ほんとに Apple のウェブクローラらしい。ただ

robots の制御指示で Applebot には言及していなくても Googlebot について指定されている場合、Apple のロボットは Googlebot に対する指示に従います。

と書いてあって、それはどうなんだ、という感じがする。

  1. トップ
  2. tech
  3. Applebot

UAからしてなんか怪しいが、210.160.8.236 からくる BOT で、DNSを逆引きすると target.microad.jp. らしい。マイクロアドを使ってるつもりはないんだけど、Adsense 貼るとくるのかな?

まともなクローラのつもりなら、UA をもうちょっとわかりやすくしといてほしいですね。まともな会社なら自社クローラのUAと目的ぐらい書いとくべきだと思います。


sitemap.xml を送信した翌日ぐらいに Googlebot からの連続したアクセスがくるようになる。Googlebot は HTTP/1.1 で Keep-Alive が有効なため、普段にパラパラくるアクセスよりも、このときのように連続してアクセスされる場合、多くの場合でコネクション時間がなくなるために高速にレスポンスを返せる。

連続したアクセスの場合、2分ぐらい Keep-Alive している。

これは結果として Search Console の「クロールの統計情報」に表示されている「ページのダウンロード時間 」のグラフにも反映されているような感じで、sitemap.xml を追加した次の日あたりで平均ダウンロード時間がかなり少なく表示される。

  1. トップ
  2. tech
  3. Google Search Console のページのダウンロード時間

金曜日の夕方ぐらいから全身倦怠感と頭痛がはじまり、微熱(37度台)に。熱は土曜日中ぐらいで下がったが頭痛は治らず、今(月曜日)でもまだ痛い。

体調不良 - 氾濫原 のあと、月曜日の昼ぐらいから気持ち悪くなった。20時ぐらいにマスク(電車が臭いので)して帰宅開始したがなんとか(というか奇跡的に)吐き気を抑えて帰宅できた。夕食は食べずに寝たが、22時ぐらいに結局トイレで吐いて、多少はすっきりした。ただ下痢なのと発熱で夢見が悪くあまり寝れず。昼食から10時間ぐらい経っていたけど、胃の中に滞留物がまだ残っていた。

子供も夕方あたりに吐いたらしくて家の中がだいぶひどかった。

火曜日は会社休んで病院。結局ウィルス性胃腸炎らしいが、待ってる以外に特に治療法もないみたいで、ビオフェルミンと吐き気止めと抗生物質が処方された。午前中は何も食べず、昼ぐらいにおかゆを食べた。火曜日中は特に良くならず。

火曜の昼あたりから今度は妻が発症した。 (定期検診で病院にいってたがついでに点滴をうけてきたらしい)。一方子供は火曜夕方ぐらいには元気になった。

水曜朝もまだ気持ち悪くて結局会社を休んだ。昼ぐらいまでに一度症状が軽くなったが、うどんを食べてしばらく経ったらまた気持ち悪く。


胃腸炎といえば一ヶ月前にも患っておりとにかく体調不良の頻度と強度が高い。なんとかしてほしい。

KX2
http://qrznow.com/new-elecraft-kx2-announced-this-thursday-at-the-dayton-hamvention-2016/

アメリカの Elecraft から KX2 という無線機が出たらしいです。KX3 よりも小さい! バンドはCW/DATA/SSB 80m〜10m と、KX3 からは 50MHz と AM/FM がオミットされています。出力も KX3 が最大15Wに対しKX2 は 10Wのようです。KX3 のオプションにあったルーフィングフィルタはKX2ではなし。I/Q 出力もなし。

肝心の重さは 13oz (369g) と、KX3 よりもさらに半分ぐらいの軽さのようです。

本体 $749。ATU が $179.95、Li-ion のバッテリーパックが $59.95。モバイルの HF トランシーバとしては競合するものがもともと殆どないんですが、KX2 は完全に競合するものがなく、コストパフォーマンスは良さそうです。とはいえ I/Q 出力がないのがちょっと時流からするとキツい感じがします。回路図みながら辿ればとれそうな気はしますが。

  1. トップ
  2. tech
  3. モバイルHFトランシーバKX2。KX3 と比較

[シチズン キューアンドキュー]CITIZEN Q&Q 腕時計 Falcon (フォルコン) アナログ表示 ホワイト V722-850 メンズ -

5.0 / 5.0

腕時計は普段しないのだが、某国家試験をうけるときに時計が必要だったのでこれを買っていた。最近はしばらく妻が使っていたが、電池切れで動かなくなってしまった。妻用には別の時計を買ったが、動かない時計だけ放置しておくのもなんなので電池を変えることにした。

元々入っていたのは SR626SW だが、100均で売ってたのが SR621SW だけだったため、こちらで代用した。この2つは厚みが違う。よって入らないことも予想されたが、100円なので試してみることに。結果的にはうまくいった。ただし、やはりすこしゆるい。

元が1000円の時計を時計屋で電池交換してもらうのもかなりアレなので、自分でやった。

この時計の場合特に特殊な工具はいらず、小さめのドライバーがあれば裏蓋をはずせる。裏蓋さえはずせば電池が見えるのでとりかえるだけだった。

余談:ボタン電池・コイン電池の型番

Wikipedia のIEC_60086の項にも書いてあるけど、型番によって化学構造(電圧や電流などの決定要因)と寸法がわかるようになっている。

SR626SW は

  1. トップ
  2. tech
  3. 時計の電池を変えた

電子工作などのときに、一時的に部品を挟んでおきたいときとかの道具として何を使っているかといえば標題にある通り逆作用ピンセットというもので、これはすなわちノーマリークローズなピンセットである。力を加えていないときはピンセット自体のバネで閉じており、挟んでおいておくことができる。

便利な特徴がいくつかある

  • ピンセット形状なので、基板の中央付近の部品など、少し遠い位置を一時的におさえたりできる
  • ほどよいバネ圧

この手の逆作用ピンセット(反作用ピンセットや逆作動ピンセットなど、用語が揺れてる)は100均でも売ってることがある。自分が使っているのも100均でたまたま買ったものだけど、ものすごく活用している。

goot 逆作用ピンセット 大 TS-17 -

5.0 / 5.0

余談:ヒートクリップ

だいぶ前に便利かと思ってヒートクリップというのを買ったことがあるが、結局ほとんど使っていない。

goot ヒートクリップ H-2SL -

2.0 / 5.0

ヒートクリップはリード付き半導体のはんだ付け時に熱から守るために使うというのが本来の用途だが、そもそもそんなに熱に弱い半導体って今時使うことがない。

そして汎用のクリップの用途で考えるとこの商品はバネ圧が強すぎて使いにくい。ので、この手の用途でも逆作用ピンセットのほうが圧倒的に使いやすい。

  1. トップ
  2. tech
  3. 逆作用ピンセットは便利

SQLite にはかなり基本的な算術演算関数しかない。追加で何かしらやるためには拡張 (Run-Time Loadable Extension) を使う必要がある。

LOG や SQRT などはオフィシャルの Contributed Files のextension-functions.c をコンパイルして使う。 http://www.sqlite.org/contrib

Ubuntu でのコンパイル。当然ながら SQLite のヘッダファイルなどが必要なので入れておく。

sudo apt-get install libsqlite3-dev

そのうえで以下のようにコンパイルする。apt-get で入れてる場合 -I などは指定しなくてもデフォルトで良さそう。必要なら pkg-config sqlite3 とかして引数を得る。

gcc -fPIC -shared extension-functions.c -o libsqlitefunctions.so -lm

extension-functions.c の冒頭にもコンパイル方法が書いてある。ただし、今はそれだと上手くいかなくて、-lmは最後につけないとダメ。罠があって、リンクに失敗しても load_extension するまで気付かない。

使うときは以下のように load_extension() を使う

select load_extension("/path/to/libsqlitefunctions.so");

Perl の DBD::SQLite で使うには

Perl から DBD::SQLite 経由で使う場合、

$dbh->do('SELECT load_extension("/path/to/libsqlitefunctions.so")');   

とする。が、実はこれだけだと動かず、以下のように謎のエラーが出る。

DBD::SQLite::db do failed: SQL logic error or missing database
not authorized 

ドキュメントにも書いてあるが、sqlite_enable_load_extension を前もって呼ぶ必要がある。

$dbh->sqlite_enable_load_extension(1);
# または
$dbh->func(1, "enable_load_extension"); 

備考

拡張の開発方法については Run-Time Loadable Extensions を見る。

  1. トップ
  2. tech
  3. SQLite で LOG や SQRT を使うには

食品安全委員会の QA によると、

大豆イソフラボンの安全な一日摂取目安量の上限値70〜75mg/日(大豆イソフラボンアグリコン換算値)

https://www.fsc.go.jp/sonota/daizu_isoflavone.html#19

と書いてある。150mg ぐらい摂取すると健康被害の影響があるかもしれないので、その半分を上限にしているらしい。ちょっと調べてみると、これが案外厳しい基準に思えたので記録しておく。

豆乳の大豆イソフラボン含有量

大豆イソフラボンアグリコン換算は以下のようになっている。食品表示にある「イソフラボン」はイソフラボン配糖体のことなので、換算して考える必要がある。

(例)大豆イソフラボン配糖体10mg×0.625 =大豆イソフラボンアグリコンとして 6.25mg

https://www.fsc.go.jp/sonota/daizu_isoflavone.html#8

ここで、一番メジャーな紀文の調整豆乳について見てみると、「イソフラボン 43mg/200ml」となっている。0.625 をかけて、26.875mg/200ml (大豆イソフラボンアグリコン換算値)

また、FAQ中の表に豆乳の含有量について記載がある。

(大豆イソフラボンアグリコンとしてmg/100g)
〜 省略 〜

食品名(検体数):豆乳(3検体)
含有量: 7.6〜59.4
平均含有量: 24.8

https://www.fsc.go.jp/sonota/daizu_isoflavone.html#6

紀文の調整豆乳はだいたい平均的な値の倍(訂正)


もし毎食コップ一杯の豆乳を飲むと、それだけで75mg/日を超える。もちろん他の大豆製品 (特に味噌や納豆) にも含まれるので豆乳単体でどうとはいえない。

なお、大豆イソフラボンアグリコンの一日摂取目安量の上限値、70~75 ㎎/日は、この量を毎日欠かさず長期間摂取する場合の平均値としての上限値であること、また、大豆食品からの摂取量がこの上限値を超えることにより、直ちに、健康被害に結びつくというものではないことを強調しておく。

https://www.fsc.go.jp/hyouka/hy/hy-singi-isoflavone_kihon.pdf

と書いてある通りなので、豆乳めっちゃ好きすぎる!!!とか、豆乳で必要タンパク質全部とるぞ!!みたいな意気込みがなければ、平均的には大丈夫なのかな。

主観による温度感

大人は毎日1日コップ一杯ぐらいは他に大豆食品をとっていても問題なさそう。妊婦や子供に対してはもっと気をつけるべきのようで、だぶん牛乳代わりに毎日飲む/飲ませるみたいな習慣はやめたほうが良さそう、というのが僕からみた温度感です。

キッコーマン飲料 調製豆乳 200ml×18本 -

5.0 / 5.0

結構みてる気がする

  • ふらいんぐうぃっち
  • 田中くんはいつもけだるげ
  • くまみこ
  • 学戦都市アスタリスク 2nd SEASON
  • 甲鉄城のカバネリ
  • Re:ゼロから始める異世界生活
  • 坂本ですが?
  • ネトゲの嫁は女の子じゃないと思った?
  • あんハピ♪
  • クロムクロ
  • マクロスΔ
  • ジョジョの奇妙な冒険 ダイヤモンドは砕けない
  • ばくおん!!

Hello darkness, my dear friend -

4.0 / 5.0

久しぶりに CD を買った。原点回帰っぽい構成みたいだけど、どうなんだろうな〜

ところで久しぶりにリッピングしようと思ったら CD ドライブがついているPCが起動せず、面倒なのでドライブを買った……

ASUS バスパワー対応ポータブルDVDドライブ 【 Windows10対応 】スリムタイプ / Windows Mac 両対応 書き込みソフト付属 ブラック SDRW-08D2S-U LITE -

4.0 / 5.0

前までは EAC でリッピングしていた記憶があるけど、今回は iTunes でリッピングした。CDDB がまだ生きてて簡単だった。

【Quick Charge 2.0対応】 Anker PowerPort+ 6 (QC対応 60W 6ポート USB急速充電器) Galaxy S6 / Edge / Plus, Note 5 / 4, LG G4, HTC One M8 / M9, Nexus 6, iPhone, iPad 他対応 (ブラック) -

5.0 / 5.0

自宅にいくつか Anker の充電器はあるが、Quick Charge 付きのものはなかった。そして、充電器いくつかあるとはいえ、旅行にいこうとすると足りなくて不便だった。

ということで上記のものを買った。ポート数多くなって便利。

Zenfone2 は Quick Charge 2.0 対応なので (特にそうとは書いてないが 9V 充電可能)、繋ぐと以下のように「急速充電中」とともに、発熱に関しての警告がマーキーで表示される。

ただ、Quick Charge 対応ポートは1つだけなので、急速充電したいときは気をつける必要がある。USBケーブルをQuick Charge ポートだけ色が違うものにした。

http://www.sqlite.org/withoutrowid.html WITHOUT ROWID 最適化について

SQLite は常に暗黙的な rowid カラムを持っていることになっている。これはカラムとして明示することもできるし、interger primary key として定義されたフィールドは暗黙的な rowid の代わりにすることができる。SQLite ではこの rowid が基本のプライマリキーになっている。

適当な数値をプライマリキーにしたい場合はこれで全く問題ないが、複合キーだったり文字列をプライマリキーにしたい場合、その表面上のプライマリキーとは別に rowid カラムができる。このケースでは表面上のプライマリキーを使って SELECT しようとすると、表面上のプライマリキーのインデックスを探したうえで、さらに rowid のインデックスを探すことになる。つまり、このケースのプライマリキーとは単に UNIQUE なインデックスでしかない。

そこそこ最近(3.8.2・2013年)からは WITHOUT ROWID をテーブル定義時に指定することで、暗黙的な rowid 生成を抑制して、プライマリキー指定した定義を「真のプライマリキー」とすることができる。

これによって

  • インデックスを辿る回数が減るのでパフォーマンスがあがる
  • 表面上のプライマリキー→rowid のインデックスがなくなるのでDBサイズが減る
  • インデックスが減るので挿入時の負荷が減る

と良いことがある。一方デメリットで気になるのは

  • 古い SQLite (3.8.2 より前) で該当DBを読もうとすると malformed database schema で怒られて読めない
  • sqlite3_last_insert_rowid() が使えない
  • インクリメンタルblob I/Oが使えない (大きなカラムを少しずつ読める低レベルな仕組み)
  1. トップ
  2. tech
  3. SQLite で「PRIMARY KEY」を《真のプライマリキー》とするには

TF-IDFによる類似エントリー機能の実装をしてみました。ほぼSQLiteですませるような構成です。

やっていることの概要

  1. エントリーのHTMLを適当なワード単位に分割
    • タグ削除とか記号削除とかしつつ、簡単な形態素解析で分割
  2. エントリごとのワード出現回数をSQLiteに全て入れる (テーブル構造的には転置インデックスとして機能)
  3. エントリ・ワードごとにTF-IDF の計算
  4. エントリごとにTF-IDFのベクトルとして正規化
  5. エントリごとに共通語が一定数を超えるエントリ複数に対しコサイン類似度でスコアを計算

ワード分割

MeCab を使った形態素解析などでもいいのですが、お手軽にやりたかったので以下のようにしています。

my $text = ...;
$text =~ s{<style[^<]*?</style>}{}g;
$text =~ s{<script[^<]*?</script>}{}g;
$text =~ s{<[^>]+?>}{}g;
$text =~ s{[^\w]+}{ }g;
my $words = reduce {
	$a->{$b}++;
	$a;
} +{},
	map {
		s/^\s|\s$//g;
		$_;
	}
	map {
		if (/[^a-z0-9]/i) {
			Text::TinySegmenter->segment($_);
		} else {
			$_;
		}
	} split /\s/, $text; 

不必要なHTML要素及びタグを削除し、記号類を全てスペースに置換したうえでスペースで分割し、日本語が含まれていそうな部分だけ TinySegmenter による簡易形態素解析をしています。

自分しか書かない日記の類似をとるので、表記揺れが殆どないことを前提に、アルファベットだけで構成される部分に関してはそのまま特徴語としたいためです。また、プログラムについて書くことが多いので日本語部分は比較的重要度が低いという見立てがあります。

転置インデックス

転置インデックスはあるワードがどのエントリに含まれるかを記録したデータ構造です。エントリー中の文章を適切な粒度で分割して、ワードからエントリーをひけるインデックスをつくります。

転置インデックスだけでも、あるワードが含まれるエントリーのリストを得るのは簡単になるので、例えば「共通ワードが多いエントリ同士は類似している」というスコアリングを採用するなら、転置インデックスのみで実現できます。

DBとしてSQLiteを使っているので、簡単な定義の単一テーブルだけを作ってなんとかしています。

CREATE TABLE tfidf (
	`id` INTEGER PRIMARY KEY,
	`term` TEXT NOT NULL,
	`entry_id` INTEGER NOT NULL,
	`term_count` INTEGER NOT NULL DEFAULT 0, -- エントリ内でのターム出現回数
	`tfidf` FLOAT NOT NULL DEFAULT 0, -- 正規化前の TF-IDF
	`tfidf_n` FLOAT NOT NULL DEFAULT 0 -- ベクトル正規化した TF-IDF 
);
CREATE UNIQUE INDEX index_tf_term ON tfidf (`term`, `entry_id`);
CREATE INDEX index_tf_entry_id ON tfidf (`entry_id`);

見ての通りですがターム(ワード)とエントリIDでユニークなテーブルです。

エントリ内のHTMLを適当な粒度で分割したのち、このテーブルに入れています。この段階では term, entry_id, term_count だけ埋めます。

INSERT INTO tfidf (`term`, `entry_id`, `term_count`) VALUES (?, ?, ?);

TF-IDF

TF-IDFは特徴語抽出のアルゴリズムです。文書中のワード出現頻度 (Term Frequency) と、全文書中のワード出現頻度の逆数( Inverse Document Frequency) から、ある文書のあるワードがどれぐらいその文書を特徴付けているかをスコアリングできます。

転置インデックスから「共通ワードが多いエントリ同士は類似している」というスコアリングを使ってエントリを抽出すると、ワード数の多いエントリ同士は類似していなくても類似しているとスコアリングされてしまいます。

TF-IDF を使って各ワードに重み付けをすれば「特徴語の傾向が似ている文書は類似してる」というスコアリングにすることができます。

まずは後述する正規化を考えずにテーブルの tfidf カラムを埋めます。

-- SQRT や LOG を使いたいので
SELECT load_extension('/path/to/libsqlitefunctions.so');

-- エントリ数をカウントしておきます
-- SQLite には変数がないので一時テーブルにいれます
CREATE TEMPORARY TABLE entry_total AS
    SELECT CAST(COUNT(DISTINCT entry_id) AS REAL) AS value FROM tfidf;

-- ワード(ターム)が出てくるエントリ数を数えておきます
-- term と entry_id でユニークなテーブルなのでこれでエントリ数になります
CREATE TEMPORARY TABLE term_counts AS
    SELECT term, CAST(COUNT(*) AS REAL) AS cnt FROM tfidf GROUP BY term;
CREATE INDEX temp.term_counts_term ON term_counts (term);

-- エントリごとの合計ワード数を数えておきます
CREATE TEMPORARY TABLE entry_term_counts AS
    SELECT entry_id, LOG(CAST(SUM(term_count) AS REAL)) AS cnt FROM tfidf GROUP BY entry_id;
CREATE INDEX temp.entry_term_counts_entry_id ON entry_term_counts (entry_id);

-- TF-IDF を計算して埋めます
-- ここまでで作った一時テーブルからひいて計算しています。
UPDATE tfidf SET tfidf = IFNULL(
	-- tf (normalized with Harman method)
	(
		LOG(CAST(term_count AS REAL) + 1) -- term_count in an entry
		/
		(SELECT cnt FROM entry_term_counts WHERE entry_term_counts.entry_id = tfidf.entry_id) -- total term count in an entry
	)
	*
	-- idf (normalized with Sparck Jones method)
	(1 + LOG(
		(SELECT value FROM entry_total) -- total
		/
		(SELECT cnt FROM term_counts WHERE term_counts.term = tfidf.term) -- term entry count
	))
, 0.0)

一時テーブルを使いまくっています。何度も同じ TF-IDF 計算をするなら効率化のため保存しておけそうなテーブルもありますが、更新処理が複雑になるためTF-IDF更新時に作っては消しています。修正が頻発するような開発初期段階では特に一時テーブルは設計変更などに強くて便利です。

TF-IDF のベクトル正規化

TF-IDF を計算して、エントリごとにエントリ中の単語数次数を持つ特徴ベクトルを作ったことになります。このあとコサイン類似度をとるわけですが、その際にはベクトルの大きさが不要なのでこれを正規化します。

ベクトル正規化はあるベクトルを方向(角度)をそのままに長さを1にするとこを言っています。コサイン類似ではエントリごとの角度だけを比較したいので、あらかじめ文書ごとのベクトルを正規化することで計算を簡単にできます。

-- エントリごとのTF-IDFのベクトルの大きさを求めておきます
CREATE TEMPORARY TABLE tfidf_size AS
	SELECT entry_id, SQRT(SUM(tfidf * tfidf)) AS size FROM tfidf
	GROUP BY entry_id;
CREATE INDEX temp.tfidf_size_entry_id ON tfidf_size (entry_id);

-- 計算済みの TF-IDF をベクトルの大きさで割って正規化します
UPDATE tfidf SET tfidf_n = IFNULL(tfidf / (SELECT size FROM tfidf_size WHERE entry_id = tfidf.entry_id), 0.0)

コサイン類似

コサイン類似はベクトル長さを無視しての角度の差を求めるためのアルゴリズムです。2つのベクトルの角度の差のコサインを求めます。例えば、2つのベクトルの角度差が0(一致している)場合、 = 1、90度(直交・相関関係なし) なら 、180度(負の相関)なら と、なります。

前もってベクトル正規化をしているのでかけ算と足し算だけで計算できます。

ただ計算が簡単とはいえ、候補エントリ数*関係するワード(ターム)の数だけレコードをひいてくる必要があるので結構大変になってしまいます。

ここの効率化方法があまり思いつかず以下にようにしています

  • エントリの特徴語を50語取得する(TF-IDF順にソートして大きいほうから50件)
  • 特徴語を含むエントリを共通語が多い順に100エントリ取得する (コサイン類似前に足切り)
  • それぞれのエントリ同士でコサイン類似度を計算してスコアを算定してソートする

特徴語の共通語が多い順で足切りをしているので、長いエントリほどここでは有利となってしまいます。また、極端に短いエントリに関しては100エントリ以上が同一数の共通語を持つ状態になる場合があり、このケースではスコアをつける前に「類似」と判定すべきエントリが確率的に足切りされてしまうので正確ではありません。

TF-IDF の大きい順に50件だけを採用しているので、正確なコサイン類似度ではありません (ベクトル正規化のときに使った次数と違う) が、無視しています。

うまくいった場合はスコアリングで上位に類似エントリが集まるようになります。

-- 類似していそうなエントリを共通語ベースでまず100エントリほど出します
CREATE TEMPORARY TABLE similar_candidate AS
	SELECT entry_id, COUNT(*) as cnt FROM tfidf
	WHERE
		entry_id > ? AND
		term IN (
			SELECT term FROM tfidf WHERE entry_id = ?
			ORDER BY tfidf DESC
			LIMIT 50
		)
	GROUP BY entry_id
	HAVING cnt > 3
	ORDER BY cnt DESC
	LIMIT 100

-- 該当する100件に対してスコアを計算してソートします
SELECT
	entry_id AS eid,
	SUM(a.tfidf_n * b.tfidf_n) AS score
FROM (
	(SELECT term, tfidf_n FROM tfidf WHERE entry_id = ? ORDER BY tfidf DESC LIMIT 50) as a
	INNER JOIN
	(SELECT entry_id, term, tfidf_n FROM tfidf WHERE entry_id IN (SELECT entry_id FROM similar_candidate)) as b
	ON
	a.term = b.term
)
WHERE eid != ?
GROUP BY entry_id
ORDER BY score DESC
LIMIT 10

これによって求められた類似エントリは別途テーブルに保存しておいて、表示時にはこのテーブルのみをひいてくる構成にしました。

  1. トップ
  2. tech
  3. TF-IDFとコサイン類似度による類似エントリー機能の実装

https://chromium.googlesource.com/chromium/src.git/+/52b672b2462d6a3751a13187a1003e6fffdfbfbd

手元で確認した限りだと、バージョン 51.0.2704.63 (64-bit) で accept-encoding:gzip, deflate, sdch, br が送信されるようになっていました。

フラグとしてはバージョン49で実装されていたみたいですが、51 (2016-05-25)でようやくデフォルトでも有効になりました。リリースノートには書いてませんが、「log」をたどると上記コミットで有効になったことがわかります。

他のブラウザだと Firefox は 44 (2016-01-26)から有効になっています。

なお、Chrome でも Firefox でも brotli 対応は HTTPS 限定です。

感想

「Chrome が新圧縮アルゴリズム対応!」みたいなニュースが流れてからだいぶ経っています。しばらくは Firefox だけ有効な状態だったので「まだかまだか」とちょくちょくチェックしていたのですが、ついにデフォルト有効になりました。これによって劇的にパフォーマンスが改善するわけではないと思いますが、accept-encoding に新しいのが加わるとわくわくします。

なおこのサイトは全体的に brotli 対応にしてあるので、開発者ツールとかで眺めてニヤニヤしています。

  1. トップ
  2. tech
  3. Chrome の brotli 圧縮アルゴリズム対応がデフォルトで有効に

h2o は compress: ON にすると、レスポンスに accept-encoding に応じたオンザフライな圧縮が有効になります。ただしデフォルト状態ではcontent-type が text/ から始まる場合と +xml で終わる場合に限られているようです

この条件に一致しない application/json などに対しても圧縮をかけたい場合、file.mime.settypes で設定すれば良いようです。すなわち

    file.mime.settypes:
      "application/json":
        extensions: [".json"]
        is_compressible: yes 
        priority: normal

このようにすれば application/json でも自動的に圧縮してくれます。

これは file. で始まるディレクティブですが、バックエンドサーバから送るレスポンスに対しても(リバースプロキシとしても)これで圧縮がかかるようになります。

  1. トップ
  2. tech
  3. h2o で特定のファイルがオンザフライ圧縮されないとき

最近 Google 関係のアプリが「バッテリを消耗させるアプリ」として通知されるんだけど、どうしろと

はぁ〜〜〜 不愉快だな〜ー〜〜〜〜ー〜 なんで不愉快なことばかり起こるのか

わかりやすい神道の歴史 - 神社本庁研修所

神社本庁研修所

5.0 / 5.0

そういえば、この本を昨年の11月〜12月ぐらいに読んだんだけど、日記に書いてなかった。書いた覚えがあるんだけどな…… これを読んでさらに調べた結果 神仏習合 - 仏教 = 純神道か? - 氾濫原 というエントリを書いた気がする。もともと古代人の神道感はどうだったのか?が気になって買ったが、これに答えはなかった (というよりも、現代では分かりようがないということがわかった) 神道の歴史と仏教は切り離せないし、神道の歴史と政治的な(具体的には天皇統治の)歴史も切り離せない。

近代以降の話はあまり興味がなくて、結構飛ばして読んだ覚えがある。


神社には生前偉業を成した人を祭っているところがあるが、現代人として自然対人間のような構造を持って見るとこれは不思議に思えることがある。実際には人間も自然の一部であると考えると不思議ではない。古代でも現代でも「すごい!」と思えるものは「神」と呼ばれている。大神神社の本殿が山そのものであったりすることからも感じられるけど、神道では人間はあくまで自然の(自動的な)仕組みの一部であることが当然のこととしてあるように感じる。

とにかく無限に下痢。

日記(ブログ)内のカテゴリを自動抽出して設定したい。いわゆるクラスタリングだと思うが、うまくできる方法がよくわからない。

動機としては「カテゴリ」とか「タグ」を設定するのが面倒なので、自動的にトピックを解析して「タグ」として抽出したい。既に TF-IDF は出しているので、上位を使えばよさそうな感じではあるが、IDF の対象が自分のエントリだけなので、一般的な「特徴語」とは違った結果になっている。

機械的にやるよりも例えば「電子工作」なら「Raspberry Pi」「電子工作」「回路」とかを含む全てのエントリを適当に検索して出せばいいだけかもしれない。かなりヒューリスティックなので自分にとっては「新しい発見」はないが、検索流入の場合は狙った似たトピックに辿りつきやすいかもしれない。

宗教とは結局のところ人生の規範を作るためのフレームワークといえよう。各宗教のことは良くしらないので怒られそうだけど、主観的には以下のような印象を持つ

  • 密結合フルスタック: アブラハムの宗教
  • 便利ライブラリの集合:仏教
  • コーディングルールのみ: 神道
  • フレームワークなし: 無宗教

なんにせよ「人生の規範」というのがある。規範なしでただ生きているという状態にはなりえない。

人間もまた自然の一部であり、心のありかたもまた自然である。自分の心をあるがままにしておくというのは自然で、制御しようというのは自然との戦いとなるが、一方戦いを行おうというのも自然のありかたである。

これは単にバランスの問題であって、どちらか一方に偏ってはいけない。「あるがまま」に成長はない。「自然との戦い」は消耗戦で、最終的には負け戦になる。