| @登山/ランニング

阿蘇草原マラニックで走る著者近影

阿蘇草原マラニックというトレランのイベントに参加した。ここ一年間は比較的真面目に走ってるので(靴を一足履き潰した)、山でも走れるのではないかと思って申し込んでみた。ただレースだときつそうなので順位を争わないマラニックというピクニックとマラソンを足し合わせたようなやつを選んだ。

感想としては一人で参加したのにめっちゃ楽しかった。ほとんどの人がグループで来てたのでボッチだとさみしいとは思いはしたが、普段走れない絶景の場所(外輪山の牧野は普段立ち入り禁止)を知らない人たちとはいえ誰かと一緒に走るのは楽しかった。イベント運営の皆さんが醸し出す雰囲気もピースフルでとても良かった。

とはいえコースは結構ハードで、公式の情報で距離 28km で累積獲得標高は 1000m とある通り( Apple Watch のワークアウトログだと距離 29.8km で累積獲得標高は 1143m だった)、普段からそれなりに走っていて、久住くらいなら楽に登れるという人でないと制限時間内にゴールは厳しいと思う。はな阿蘇美からスタートして国造神社で折り返すコースだったが、国造神社のエイドステーション出発の制限時間が2時間50分で、上り500mある14kmの距離を3時間弱で到達するのは歩きでは間に合わず、平地や下りはちゃんと走らないといけない。外輪山は平らなので走りやすいが、阿蘇谷から外輪山に出るまでの登りが結構きつい。もし来年出ようという方はそれなりのトレーニングが必要だと思います。

今回自分は往路は2時間20分、復路は2時間15分(国造神社のエイドステーションで25分くらい休憩)かかってたようだ。往路の最後で足がつり、復路はあまり走れなかった。外輪山に上がったあとはぼちぼちジョギングみたいなペースで走っていたが、外輪山を降りきって樹林帯に入ったところからまた足がつり、最後の 3km くらいは走っては休みを繰り返しながらゴールした。これまで最長でも 10km しか走ったことなく明らかに準備不足だった。前週に叶岳周回 9km を走って足つりとか疲労とかなかったので 20km くらいまでなら余裕だろうと思っていたが折り返し地点手前で足がつったので普段からもうちょい長い距離を走らないとダメそうだ。

トレランは袖なしシャツとかサンバイザーとかハイソックスとかギラギラしたサングラスとかのイメージがあって苦手だったけど、最近はサンバイザーとギラギラサングラスの人は減っていて(袖なしシャツとハイソックスの人はいる)おしゃれな人が多くて華やかだった(女性も多い)。ピークを目指さずトレイルを走るのが目的なのも新鮮で良かった。

参加費は行政の助成を受けているようでわずか1000円で、エイドステーションおよびゴール後の振る舞いや参加賞の温泉入浴券がもらえることを考えるとはちゃめちゃに安い。来年は価格が上がるだろうけど数千円なら全然払う価値あるなと思えるイベントだった。何より阿蘇の外輪山の牧野を走れるのはとても貴重。もし来年も開催されるのなら是非また参加したい。

| @技術/プログラミング

MySQL だけでお手軽に全文検索ができるということを知らなかった。 MySQL 5.6 から入っていたようだった。 Tantivy および Tantiny を使ったやり方を以前記事に書いてサイトで実装しているが、 MeCab によるトークナイズでは二文字の熟語がセットになって四文字になっているようなパターンを取り逃すことがあった(「関連記事」は「関連」と「記事」に分割され、「関連」や「記事」というキーワードで検索したときにはヒットするが「関連記事」で検索するとヒットしない)し、記事追加時の検索インデックス更新処理が不要( MySQL にレコードが追加されたときに勝手に更新される)なので試してみることにした。

やり方は以下の記事を参考にした。

最初にデータベースに全文検索用のインデックスを作成した。

ALTER TABLE `entries` ADD FULLTEXT INDEX index_entry_fulltext(title, body) WITH PARSER ngram;

その後、検索部分のコードを書き換えて以下のようにした。

class Entry < ActiveRecord::Base
  scope :search,
        ->(words) {
          return all if words.blank?
          where('MATCH (entries.title, entries.body) AGAINST (? in BOOLEAN MODE)', words)
        }
end

めっちゃ簡単。

このブログは記事数が 1500 記事くらいなのでぶっちゃけ LIKE 検索でも実用的な速度( 100msec 以内)で結果を取得できるが、 FULLTEXT インデックスを使うと 10msec 程度で結果を取得できる。

ただし Tantivy と比べて劣る点もあって以下は注意が必要。

  1. なぜかわからないが Vim で検索すると何もヒットしない。また Rails で検索すると Rails について触れていない記事もヒットする。 ngram によるインデックスというのはこんなものなのかもしれない。検索ワードが日本語のときはいい感じに結果が表示される。
  2. 複数のテーブルにまたがるデータを一個の検索インデックスにまとめることができない。例えば Tantivy のインデックスは記事のタイトル、本文、カテゴリー、タグをインデックス対象としているが、 MySQL の FULLTEXT インデックスだとテーブルごとにしかインデックスを作れないので(当たり前)、複数のテーブルにまたがる検索をするときにはテーブルを JOIN するしかない。 OR マッパーを使っている場合には利用しづらい。

1 の問題に関しては、 MySQL 5.7 からインデックス生成時の PARSER に MeCab などを指定できるようになったのでそうすると回避できるかもしれない。ただし MeCab のインストールや設定を行う必要があるので要注意。

2 の問題に関しては全文検索システムを入れた方が良さげ。 Tantivy であれば非常に簡単に導入できる。

現状、このサイトでは右上の検索窓から検索したときのインクリメンタルサーチとアーカイブページでの絞り込みは Tantivy を、インクリメンタルサーチの結果で必要な情報が得られなかったときの「全文検索する」と 404 Not Found ページの検索は MySQL の全文検索を使うようにしている。

二つの検索

| @ブログ

404 ページ、昔はそもそもなくて 404 Not Found ステータスを返すだけだったり、あっても「見つかりません」というだけのものが多かったけど、最近はサイトマップ的なコンテンツや代替となるコンテンツを表示するサイトも見かける。というわけでこのサイトでもやってみることにした。

このブログの URL は /YYYY/MM/DD/slug という形式になっている。パスの /YYYY/MM/DD の部分はお飾りで、実際は slug がユニークになっているので slug で表示すべき記事を判定している。

よくあるのが記事を公開後、 slug 部分にタイポを見つけて変更するというケース。しかしすでにその時点で記事が Twitter などでバズってたりすると、 Twitter で共有されている記事を見てやってきた人が 404 Not Found ページを見ることになる(この前の「不便になるインターネット」がまさにそうだった)。それはまずいので slug のタイポを修正すると同時に Nginx の設定ファイルをいじってタイポ修正前の URL から修正後の URL へリダイレクトするようにしていた。しかしリダイレクトごときでサーバーの設定ファイルを修正して root 権限でリロードするというのはめんどい。 SSH でログインもしなければならない。大げさすぎる。

というわけで思いついたのがこの機能で、 Ruby でクラス名やメソッド名をタイポしたときに正しい候補を表示する did_you_mean.gem を利用した。存在しない slug で URL を開くと以下のように候補が表示される。

404 Not Found

コードはこんな感じ。

# Helper
def not_found_candidates
  @not_found_candidates ||=
    begin
      slugs = Entry.published.where.not('slug REGEXP ?', '^[0-9]+$').pluck(:slug)
      spell_checker = DidYouMean::SpellChecker.new(dictionary: slugs)
      current_slug = request.path_info.split('/').last
      slug_candidate = spell_checker.correct(current_slug)
      Entry.published.where(slug: slug_candidate)
    end
end

# View
- if not_found_candidates.any?
    %p Did you mean?
    - not_found_candidates.each do |candidate|
      = link_to candidate.title, candidate.link

データベースから slug 一覧を取り出して辞書とし、 DidYouMean::SpellChecker に食わせて似たページの候補を取得して表示する。タイポありのページを訪れた人はワンクリックしなければならないという手間が増えるが、これでタイポを修正したときに面倒なリダイレクトの設定をする必要がなくなった。

なお 404 ページには検索窓や最近の記事、カテゴリー一覧も表示して回遊性を高めている。

404 Not Found ページ

| @技術/プログラミング

Homebrew で入れた SQLite で load_extension() が動かなくなっていた。

どうも以前は --enable-loadable-extensions でビルドできていたらしいが、 Homebrew 全体でオプション指定をできなくする変更が 4 年前にあったみたいだ。

オプション指定できるのは UX として良くない(ビルド済みのバイナリをダウンロードできない)し、オプション指定を Homebrew のチームでテストしていないからだそう。

TF-IDF で関連記事を表示する機能は SQLite の拡張に依存していたので、最近 Homebrew で入れた SQLite だと機能が動かなくなってしまった(エラーが発生する)。

対策としては自分で SQLite をビルドするしかない。 Mac でそれやるのは面倒なのでこういうのは全部 Docker でやることにした。

Homebrew のウリはビルド済みのバイナリをサクッとダウンロードできてローカルでビルド不要なところではあると思うが、ちょっと凝ったことをしようとするとソースコードをダウンロードしてきて手元でビルドしないといけなくなってしまった。依存パッケージをまとめてインストールできてたことも便利だったんだけど、カスタムインストールをしたいときは昔みたいに依存関係を自分で調べて都度都度インストールしていかないといけなくなった。開発チームの考えもわからなくはないがちょっと残念だ。

ちなみに Homebrew のリポジトリ( homebrew-core )の過去のコミットログを調べるのがめっちゃ大変だった。 tig Formula/sqlite.rb しても 3 分くらい反応がなかった。 git log -Sextension Formula/sqlite.rb してそれっぽいコミットのハッシュ値を見つけて GitHub で検索して何とか上記の Pull Request と Issue に辿り着いた。超巨大なプロジェクトのソースコード管理は Git でやると大変そうだ。

| @WWW

博多駅前

サウナイキタイのサ活と Google マップのクチコミには結構乖離があることに気が付いた。自分が行ってめっちゃ気に入って、サウナイキタイでも好意的なサ活が多い佐賀の KOMOREBI の評価が Google マップでは低い。サウナにも水風呂にも入らないのに物価の安い九州(温泉に 500 円で入れる)で風呂に 1100 円払えと言われたら高すぎると感じる人もいるのだろう。しかも混んでるし。

Google マップのクチコミはとても便利だけど、一つの施設で二つ以上の役務を果たしてる場所や客を選ぶタイプの店は評価が下がると思う。本当は自分の好みにピッタリの施設が低評価となっている可能性がある。なので評価が低いからと切り捨ててしまうのではなく、自分と似た属性の人からのクチコミを探し出して読むと実際のところの雰囲気がわかるかもしれないが、それは難易度が高すぎる。

今日のプラットフォームサービスには不幸なマッチングを回避する仕組みが必要なのだと思う。あなたはサウナ好きだからこの施設は満足できますよとか、ここは常連向けですよ的な情報が簡単にわかるようになるとか。

ただ一方でそれは Instagram で自分のようなおっさんに微エロのリール動画ばかり表示されるようなレコメンデーションとアルゴリズムによる支配がいろんなサービスに広まるということであり、それはそれで残念ではある。

もう一つの選択肢としては、釣り SNS や登山 SNS が出来たように、レビューサイトも専門分化させていくアプローチが考えられる。自分のようなアルゴリズムやレコメンデーションにあらがいたいウェブ縄文人にはそういう進化の方が向いているかもしれない。

というようなことを思ってたら一つ前の記事に言及しつつ伊藤直也さんが似たようなことを書いていた。

2ch みたいにごちゃごちゃカテゴリーがサイドバーに並んでいるのには普通の人には難しすぎるので、釣り掲示板がツリバカメラになり、登山掲示板がヤマレコや YAMAP になったんだろう。考えてみるとサウナイキタイもサウナに特化した SNS ・情報サイトだ。

サウナイキタイに話を戻すと、サウナイキタイはローカルルール満載の町銭湯に対する評価が甘いという特徴もある。サウナイキタイでベタ褒めされてる施設(「番台のおばあちゃんが優しい」、「常連の方達とほのぼの交流」などなど)が Google マップではボロクソに書かれてたりする(「番台は意地悪ばあさんそのもの」、「彫り物を入れたヤクザや半グレの常連でサウナが占有されていて入れない」などなど)。ネガティブなことは書かないというコミュニティポリシーの影響なんだろうか(以前、サ活でドラクエ集団がいたと書いたら公式アカウントから警告された)。サウナイキタイだけ見て福岡の銭湯サウナに行ったら刺青を彫った怖い人から凄まれながら入浴しなければならないという体験をしそうだ。

レコメンデーションにしても専門分化にしても塩梅が難しい。レコメンデーションはやり過ぎるとただの偏見にしかならない( 40 代男性はおっさん → おっさんはエロが好き → 微エロ動画見せとくか)し、専門分化して蛸壺化しすぎると一般的な感覚と離れたレビューが集まるようになってしまう(怖い人だらけの銭湯サウナが高評価)。昔のインターネットではこんなことに悩むことはなかった。

昔のインターネットは良かったとばかり言ってもどうにもならないが、インターネットを飯の種とする者の一人として人が増えたいまのインターネット特有の問題をどうにかしたい。

| @WWW

三苫海岸

ソーシャルメディアやニュースサイトに毎日新しいコンテンツが次々に投稿されるので、インターネット上の総情報量は増えていっているはずだが、 20 年前と比べてアクセスできる情報の種類は減っているのではないかと感じる。いま何か情報を得ようとしたときに Google は以前ほど便利ではなくなってきている。 Google がキュレーションした情報にしかアクセスできないからだ。誰にもフィルタリングされていない生の情報にアクセスしようとしたら Twitter 検索の方がよっぼどよいと感じるくらいだ。

昨年末、 40L の登山用バックパックをニュージーランドのショップから購入した。日本でも売っていた商品だが、国内の正規取扱店では売り切れてて個人輸入で購入するしかなかった。商品名で Google 検索しても日本語のページしかヒットしないし、在庫ありとして表示される楽天や Amazon のページには怪しい業者が定価の何倍もの価格でふっかけて販売しているケースがほとんどで、 Google から直接海外のショップのページに辿り着くことができない。メーカーのウェブサイトから各国の取り扱い店をたどってようやく販売しているページを見つけてメールで問い合わせて購入することができた。

15 年くらい前、日本から patagonia.com にアクセスすると patagonia.jp にリダイレクトされて、アメリカで 10000 円くらいで売られているものが日本では 1.5 倍の 15000 円くらいになってて日本人はぼったくられている、というようなことを書いている記事がバズってた(ちなみにいまも patagonia.com を開こうとすると patagonia.jp にリダイレクトされる1)。当時は日本人が価格差に気がつけないようにしているのは邪悪だということで攻撃されていたのはパタゴニアだけだったが、現在では Google が似たようなことをやっていて、インターネット全体でパタゴニアと同じようなことが起こっている。日本人(日本の IP アドレスから日本語設定のブラウザーを利用している人)が海外のショップから直接物を買おうと商品名で Google 検索しても、海外のサイトはほとんどヒットしない。日本人は日本語のウェブページしか見させてもらえない。

日本語のページであっても、すべてが検索結果に表示されているわけではない。何か商品について調べようと Google 検索しても結果に出てくる店は決まっていて、 20 件程度表示されたあとにそれ以降の情報を探すことができない。楽天や Amazon 内の情報のほか、 BASE や STORES 、カラーミー、 Shopify などといった割と利用者が多いカートを採用しているページは Google ショッピングの一覧に表示されるが、そうではない自前の CMS で構築されているような地方のショップのサイトなんかは結果に出てこない。

昨シーズン、ARC'TERYX の Motus AR Hoody というパーカーを買ってとても使い勝手が良かったので、今シーズンも色違いを買おうと探してみたら主要なサイトではすでに売り切れていた。一昨年も GRiPS のサイト(カラーミーで構築されている)に掲載されたタイミングでは買い逃していてネットの海をさまよって何とか辿り着いた地方のショップのサイト(メジャーなカートシステムではない独自システムのサイト)で購入することができた。まさかそんなことあるまいと思いながら今シーズンもそのサイトを訪れて探してみたところ、何とよそでは売り切れて定価の 2 倍とか 3 倍の値段で売られている Motus AR Hoody が定価で販売され在庫が残っていた。こういう例は一度や二度ではなく、何度か経験した。

インターネットに情報が増えすぎて、 Google としても検索結果に表示するページは絞るしかないのだと思う。すると Google にとってクローリングしやすく、サイトの更新を追っかけやすいサイトばかりが検索結果に表示されるようになる。サイトのレスポンスが遅かったり、構造がいまいちイケてないサイトは検索結果に出てきづらくなる。その結果、同じようなページばかりが検索結果に表示され、一部のサイトにだけトラフィックが集中して独自サイトにはアクセスが集まりづらくなってきている可能性がある(だから自分が買ったショップのページでは Motus AR Hoody のような人気商品が売れ残っていた)。

インターネットは情報の非対称性を下げて、より取引を効率化するものだと信じてきてが、どうやらそうではないようだ。一部のページにだけアテンションが集中し、むしろ情報の非対称性が高まっている。あっという間に定価で売られていた商品が売り切れてモノの値段がつり上げられ、一方でアテンションを集められないサイトでは売れ残ってセールになっている。経済学のセオリー通りなら、市場の原理が働いてギリギリに近い競争均衡価格で商品は取引されるはずだが、実際には逆の現象(正規販売店による定価販売と一部の転売業者による価格つり上げ販売)が起こっている。

EC サイトだけでなく、ブログや個人のウェブサイトでも同じような問題が起こっているのではないかと感じる。 Google のコアアルゴリズムアップデートで自分のブログも随分 Google 検索からの流入が減った。

2015 年からの月ごとの検索流入の推移

実際には存在するのに、 Google から不遇されて存在しないことになってしまっているウェブサイトがインターネット上にはきっとたくさんあるだろう。

昔のインターネットのような、検索結果をたどればたどるほど新しい情報と出会えていた頃が懐かしい


  1. 2023-01-18 訂正: patagonia.com から patagonia.jp へのリダイレクトは2023年1月18日時点では機能していなかった 

| @雑談

福岡県水産海洋技術センターから見る脊振山

年が明けてからやるのはどうかと思うが、タイミングがなかったので今さらながら 2022 年のふりかえり。

ランニング

2022 年はよく走った。これまでも何度か走ってると書いていたが、ちゃんとグラフにしてみると 2022 年の 8 月までは大して走っておらず、 2022 年の 9 月から本腰を入れて走るようになったようだ。

オレンジ色の線が 2022 年のもので、9 月から傾きが急になっている。 9 月は月間 110km 走った。月間 100km はフルマラソンに出られる基準のようなのでこの頃は調子こいていた。

ランニングの頻度を上げるのに役立ったのが計画表だった。それまで漫然と走っていたが、漫然と走っていると月間 100km も走れないことに気がついた。自分は一回 5km 走っているが、それを気が向いたときに週 2, 3 度やるだけだと月間 50km くらいにしかならない。きちんとランニングした日と距離を確認していかないと目標には辿り着かない。ログは Apple Watch で取得しているが、統計データとしては見られない。なので HealthFit というアプリを使っているが、 HealthFit では目標設定ができないので Numbers でシートを作って管理することにした。これは元はてなのディレクターの二宮さんの記事の真似。

こちらが年間の週次のランニング計画。

年間ランニング計画

こちらが月間の日次のランニング計画。

月間ランニング計画

月間のシートに日次の目標と実績値を入れると、年間の週次のシートに自動反映される仕組みになってる。これによって自分は一週間に何 km 走る予定で現在目標に対してどのくらい達成しているのかを確認できるようになる。

37signals の本を読んで、計画を立てるのはアホだ、数値目標なんて意味がない、未来を先読みすることはできない、という発想に影響されて計画を立てたりするのは何となく良い印象を持っていなかったけど、月間何キロくらい走りたいとか、ベンチマークとなる具体的な数値目標がないとなかなか実績は積み上がっていかないと思う。もちろんまだ走ったことがない状態でいきなり月間 100km のような目標を立てるのは愚かだと思うが、そこそこ走れるようになってきたら(自分の力がわかるようになってきたら)計画を立てたり数値目標を設定したりするのは悪くないことだと思う。

9 月にがむしゃらに走ったおかげか、最近は走るペースが一段速くなっていて、 1km を 5 分台で走れるようになってきた。今年はちょっと色気を出して初心者向けのトレランの大会に出てみようかと思ってる。

登山

4 月に脊振山系全山縦走、 11 月に九州脊梁に行った。夏に北アルプスを予定していたが、ちょうどコロナにかかって行くことができなかった。何にせよ最近は山に登るのよりも近所を走る方が楽しいので山への足が遠のいた一年だった。登山時の体力作りで走り始めたのに本末転倒している。

仕事

一昨年は結構でかい成果を出せたが 2022 年はぱっとしない一年だった。反省したい。

生活

iPhone を買い換えて 11 から 14 Pro になった。カメラが三つになって望遠の写真を撮れるようになったのが便利。 Dynamic Island も便利。タイマーかけてるときに常に Dynamic Island で残り時間を確認できるのは相当便利。 164800 円払ってよかった( 36 回ローン)。

サウナにハマってぼちぼち行っていた。サウナーの人たちのように一週間に何回も通うというほどではないが、金曜日の仕事帰りにサウナまで走って行ってサウナに入って帰るというのを何回かやった。花金感が出て良い。

2020 年に車を買い換えたがあまりドライブしてない。アメ車なので燃費が悪い、コロナなので出かけづらい、などいろいろあるが、せっかく車を買い換えたのに使わないで置物になってるのはもったいないので有効活用したい。

そういえば 2022 年は YouTube をよく見た。 YouTube Premium に入ったので広告が表示されなくなり、邪魔が入ることなくとてもなめらかに動画を視聴できるようになった。 Netflix はあまり見ていなかったので解約し、 YouTube で素人が上げる動画をよく見た。ストーリーのない、ただ料理をしているだけとか、ただ穴蔵を掘っているだけの 15 分くらいの動画を見るのがちょうどよい。 Netflix の動画は長いし、ことあるごとに金、暴力、セックスを意識させられるので見るのがきつい。こってりしすぎている。

ブログ

Tantivy を導入して全文検索できるようにした。検索も見た目を変えてインクリメンタルサーチできるようにしたりした。年に一回くらいはバズる記事を書きたいが、 2022 年は不作だった。反省したい。

総括

仕事や生活、ブログは本当にぱっとしなかった。その代わり走ることを頑張っているのかも知れない。ランニングや登山は、大して頑張って生きていないのに走ったり山に登ったりするとめっちゃ頑張ってるかのような錯覚を得られて自己肯定感が高まる。仕事などでもちゃんと成果を出しつつ走ったりできたら良いのだけど自分の場合は逃避になってるような気がする。しかし走るのをやめたら仕事で成果を出せるかというとそうでもないし、遺伝的に糖尿病のリスクがあるので発症しないように死ぬまで走り続けるしかない。 2023 年は運動しつつ仕事やブログ書きでも一定の成果を上げたい。あともうちょいサウナに行きたい