schemalexの神機能

この記事はOSS紹介 Advent Calendar 2017 の 5日目の記事です。

schemalexとは

schemalex/schemalex

Generate the difference of two mysql schema

と書いてある通り、2つのMySQLスキーマの差分(ALTER)を表示するプロダクトで、@soh335が中心となって開発をしており、 僕も業務でも使っているのでたまにpatchを送っています。

perlだとSQL::Translator::Diffにあたり、 このschemalexはMySQLにしか対応していないのですが、Go製のプロダクトのためバイナリが提供されているため、コマンドラインツールとして簡単に使えるところがSQL::Translator::Diffより使いやすいと思って気に入っています。

紹介したいこと

@lestrratによる#34 のPullRequestが個人的にライフチェンジングだったので是非紹介させてください。

この機能でできるようになったことはスキーマの比較方法の拡張です。

これまでは以下の用にbefore/afterのsqlを用意して比較していました。

$ schemalex /path/to/before.sql /path/to/after.sql

このPRで以下の3パターンの入力に対応されました。

1. mysqlデータソース

$ schemalex /path/to/file mysql://user:password@tcp(host:port)/dbname

2. gitのコミットハッシュ

$ schemalex local-git:///path/to/repo?file=foo.sql&commitish=deadbeaf /path/to/file

3. 標準入力

.... | schemalex - /path/to/file

何が嬉しいの

例えば、普段業務においてDBのmigrationでgitのコミットハッシュと比較するgit-schemalexGitDDLを使っているのですが、 残念ながらうっかり直接ALTERをしてしまったり、ALTERの途中でFK制約に引っかかったりしてしまい、あるべき状態になんとか戻したくなったりすることがあります。 また、gitlogの任意のバージョンに戻したい場合もあります。

そういった時に下記の用にコマンド一発でalter分を出力できるのです。

schemalex mysql://user:password@tcp(host:port)/dbname /path/to/file > revert.sql

最後に

あんまり必要性にピンと来ないかもしれませんが、手元の環境がーーー!テストデータの入れ直しはしたくなーーーーい!となったときに、助けてくれるツールだと思いますので、 ご活用いただけますと幸いです。

マコピー書いたよ!

ISUCON7で優勝しました

まずは、素晴らしい問題と素晴らしい会場、素晴らしいインフラを提供してくれた運営に関わる皆様、本当にありがとうございました。
また、腰の重い僕を引っ張ってくれた会社の同僚の皆さんありがとう。

ISUCON7に同僚のmizkeisuzukiとMSAで参加して優勝してきました。

勝因はこれです。


というのは冗談ですが、初参加のため過去の大会のことはわかりませんが、今回の問題は僕たちのチーム編成にとってとにかく相性が良かったというのが大きかったです。
チーム編成や基本的な作戦については下記の予選のブログに書いていますが、今回の問題はインフラはほとんど関係なく、膨大かつ複雑なアプリをどうするかみたいな感じで、手数を打てる僕らに有利だったと思います。

 

ken39arg.hatenablog.com

問題概要

決勝の問題はクッキークリッカーのソーシャル版で複数のユーザーでルームを共有できるというもの。
ポイントは以下

  • 扱う数字が64bitintの桁数を遥かに超える巨大な正数を扱うこと
  • サーバーは4台構成
  • 通信の殆どがWebSocketでルーム毎にサーバーを固定することが可能
  • 更新系アクションaddIsuとbuyItemの成功数がスコアに直結する
  • 参照系getStatusが重く1秒以内に結果を返せないとwsクライアントは切断する
  • websocket以外がアクセスするサーバーは予選同様チェックボックスで選べる

より詳しいことは本戦の問題が公開されたら見てください

事前準備

予選での反省を生かして本線ではモニタリングにmackerelを入れるだとか、静的型付け言語であるGoでも検知できないような文字列のタイポをしづらくするだとか、Redisを迷いなく使うための準備だとか、そういうのをするつもりでしたが、予選後に突然仕事が忙しくなってしまい何一つ準備することはできませんでした。

結局予選と同じchefを準備する程度のことはsuzukiがやってくれたようですが、予選とは異なり事前に集まったり、issueにメモしたりだとかは一切できませんでした。

あと、ノートとかホワイトボードを用意しようと思っていましたが、それすらも忘れてしまい、かろうじてsuzukiのカバンに入っていた病院の明細の裏が僕達のホワイトボードの代わりでした。

結果論ですが、そうしてノープランで挑んだことが、いつもどおりやるという結果につながり、そのいつもどおりがこの結果に繋がったと思っています。

やったこと

mizkei が既に書いているし、suzukiも書くだろうけど、何をやったのかをいろんな視点で見るのは興味深いと思うので僕も書いておきます。

ドキュメントをよむ
  • redisのincrby使うと良さそうだ!
  • 1ルーム1サーバーにしてスタンドアロンで完結できそうだ!
  • addIsuとbuyItemがスコアになる!
サーバーログイン〜環境準備

やること

  • まずサーバーに入って準備
  • mizkeiはコードリーディング

ログ

  • 自分のPCの.ssh/configを設定し `ssh isu{1..4}` でログインできるようにした
  • 10:30 用意したchefなどをgit pull (suzuki)
  • 10:30 webapp/go db以下をgit push (suzuki)
  • 10:42 isu1〜4の/etc/{nginx,mysql} を集めてgit push それぞれシンボリックリンクにする(ken39arg) → 一応shellコマンド
初期実行

やること

  • alpなltsvとslowlogを出し、golangでアプリを動かし最初のスコアを見る

ログ

  • 11:00 nginxのログフォーマットをltsvに変更(ken39arg)
  • 11:05 MySQLのスローログ設定(mizkei)
  • 11:08 初期設定のままisu1,isu2,isu3に向けてベンチ実行 Score:7211

感想

  • slowlog特になさそう
  • websocketのせいででaccesslog意味ねーみたいな感じでさらっと流す。
  • suzuki曰くdbがボトルネック

アプリ修正 その1

戦略

  • Redisのincrbyとか使え無さそうという初見であるが一応Redisを入れておこう
  • キャッシュ戦略を取りやすくするため1ルーム1サーバーにしよう
  • roomnameからの逆引きは、普段DBやredisをシャーディングする時の用にincrementな採番をして永続化できるようにroomnameをuniqにしてAUTO_INCREMENTなidを持つテーブルで行く
  • とりあえず明らかに無駄なクエリを削ろう

ログ

  • 11:40 deploy するためのmakefile作成(suzuki) ※ミスがあり12:35にfix
  • 12:02 getCurrentTimeはdb見ずに`time.Now().UnixNano() / time.Millisecond`に (ken39arg) Score:5103
  • 12:30 mitemは初期化時にmapにキャッシュ(ken39arg) Score:10401 ※ ただしこのスコアに再現性はなかった
  • 13:07 ルーム:サーバーの1:1対応 (mizkei) Score:9784 ※ Scoreは変化無いが後の戦略のためmerge
アプリ修正 その2

戦略

  • additem buyitem getscore で呼ばれるupdateroomtimeはオンメモリ管理に変更しforupdateのlockを削る
  • addIsuの確定スコアをdbに保存し過去分のスコア計算を削る
  • gzipとか基本的なnginxの設定見直しをしておこう

ログ

  • 13:25 nginxの設定変更 (suzuki) ※ スコア落ち不採用
  • 13:40 addIsuの確定スコアをDBで管理するのは難しいので諦めることにする(ken39arg)
  • 13:55 updateroomtimeのオンメモリ化 (mizkei) Score:4836 ※スコアが減ったのでmerge見送り

アプリ修正 その3

戦略

  • その2は全滅だったので冷静にpprofを取ろう
  • オンメモリ前提でキャッシュしDBはメモリ復元に使うことにする

ログ

  • 14:17 pprof (mizkei)
    → big.Int 周りをなんとかすべしな感じ
  • 14:36 ルーム毎の共有メモリと管理方法を確定(ken39arg)
  • 14:42 GetPower GetPriceをcount毎にcache(mizkei) Score:8654 ※スコアが減ったのでmerge見送り
  • 15:05 addingsをキャッシュし`INSERT ON DUPLICATE`,`SELECT FOR UPDATE`,`UPDATE`の3クエリをINSERT or UPDATE 1回で済むようにした (ken39arg) Score:7746 ※スコアが減ったのでmerge見送り
  • 15:29 nginx.conf のgzipstaticなどを間違いないのを修正(ken39arg) Score:9259
アプリ修正 その4

戦略

  • その3やその2で入れたキャッシュ関連の修正はどう考えても効かないわけが無いのに、なぜスコアが全く伸びないのかということに思い悩む
  • mizkeiがレギュレーションを読み直し、そもそもgetScoreを1秒間隔で返せなくなるとベンチが上がっていかないという記述に気づく。
  • これまでの変更は効果を発揮する前に足切りにあっていた可能性が高いということで、ダメ元でお互いのレビューでOKなものはmergeしつつ、getStatusを最適化していこうという話をする。
  • インフラ担当のsuzukiは暇させてしまっていたのでhttp2試しますみたいなことで、よろしくみたいなノリ

ログ

  • 15:47 14:42に見送ったGetPower GetPriceのキャッシュ化PRをmerge(mizkei) Score:7012
  • 15:57 13:55に見送ったupdateroomtimeのオンメモリ化PRをmerge(mizkei) Score:7921
  • 16:07 15:05に見送ったaddingsのキャッシュPRに確定addingsを纏めて総量を減らす変更を加えてmerge (ken39arg) Score:6404
  • 16:27 buyingsもオンメモリ化(ken39arg) Score: 8744
  • 16:31 getStatusでDBへの参照がきえたのでtxなどDBを触っているところをすべてなくす(ken39arg) Score:5773
  • 16:36 1000回loop内の`totalMilliIsu.Cmp`で使っている`new(big.Int).Mul(itemPrice[itemID], big.NewInt(1000))` をloopの外にだしloop内で初期化させないようにする(mizkei) Score:48745
アプリ修正 その5

戦略

  • isu1〜3のCPUが圧倒的に支配的になりDB担当のisu4がスカスカになったので、isu4にもwebsocketを向ければ多分特別賞超えられるからやろう
  • 再起動時にキャッシュを復元するのを据え置いていたが、50000を超えたらとりあえず復元に取り掛かろう
  • getScoreまだイケるところ無いか探ろう

ログ

  • 16:45 isu4にもwebsocketを2:2:2:1で向ける(ken39arg) Score:57103

  • 17:00 再起動時にキャッシュを復元 (ken39arg) Score:61750
  • 17:30 big.NewInt(1000)を、グローバルにもつ (mizkei) Score:63404
再起動試験そしてチャレンジ

戦略

  • とりあえず再起動試験をしてScoreを確実にしていこう
  • 他のチームも壁を作ってくる事を想定し優勝スコアは15万くらいと予測、その結果まだあげる必要があるだろう。
  • 予選では「チャレンジしない」と言っていたが決勝は「チャレンジしよう」ということで、最後の最後まで粘ることにする。

ログ

  • 17:45 logとpprofをけす(ken39arg) Score:62871
  • 17:50 最高スコア(たまたま) Score:65218
  • 18:00 getStatusにおいて結果をキャッシュし、最後の取得時間とcurrentTimeと比較し同じならキャッシュを返す(ken39arg) Score:49963 ※不採用
  • 18:18 最終スコア Score:64847
もっとこうしたかった

Schaduleという名前にあるように、addItem,buyItemのタイミングでreqTimeから1秒分のスケジュールを更新するという戦略をゴールにしていたら、getStatusのタイミングでは参照だけで済むようになり、1000回ループの回数を更新アクションの回数と一致させることができ、最低限に抑えることができたはずだと思う。

そこで2回めのブレイクスルーになるのだが、次第にaddItem,buyItemの処理に時間がかかるようになり、そこの改善という一歩先に勧めたと思う。

おそらくそこまで行ったら、今度はbuyItemをaddItem同様にitem毎にmergeするとcalcの計算がまた早くなりじわりとスコアが上がる。

最後はaddItem,buyItemがかなり短い周期で来るようになるのでbuyItem,addItem直後のスケジュール作成を止め、スケジュール更新キューみたいなものにぶち込んでいってその更新キューの間引き調整で最後はねるみたいなストーリーが待っていたと思う。

あくまでも勘であるが、、、

こうやって勝っても悔しさが残って後を引きづられる感じ、ISUCONって初めて参加したけどとっても素晴らしいものだったんですね!!

感想

途中までPRの効果を確認できなくて、マージできてなかったんだけど、アプリ担当の自分とmizkei の関係性から、お互いレビューしあって問題なければ結果が出なくてもマージしていくという決断をしたのは大きかった。

この辺は普段の業務でも同じチームでお互いレビューしあっているということの強みが本当に出た。

あと、予選は1人のミスをみんなで解決するみたいなロスがあったけど、ダメな時はレビューに丸投げして、また別のことに取り組むみたいないい意味の丸投げ体制が取れたのは良かった。

基本的に1回でいい処理は一回しかしないという普段から気をつけていることを黙々とこなした結果ブレイクスルーを踏めたから勝てた。

まあ、正直踏んだ修正が僕じゃ無くてmizkei だったのは悔しいけど、さっさとgamestatusの1000回ループを改善して、個別修正の効果を実感できるような流れだったらもっと楽しかった。

予選もそうだけど、基本的に普段やらないことはやらないで、やってることをガンガンやり、お互い信頼するというのができたのが良かった。

今回のようにpprof以外にすぐできる有効なボトルネック特定手段が無いようなときは、コードリーディングで確信は無いけどほぼ間違いなく効果があるであろうことにアタリを付けて、それを改善するということは業務でもよくある。特に小一時間で終わるような小さな修正に対して、いちいち絶対に効くか確認をするなんてことは大抵しない。影響が無いように小さな単位でPRしmergeしていくことでローリスクな手をたくさん打てる。

僕たちは普段からそういう感じでやっていることが多いので、ハイリスクなcalcStatusのアルゴリズム変更のようなトライをせずに、コツコツと積み重ねて結果を残せたのは今後の自信にもつながって誇らしく思う。

 

さて、これで私の所属するKAYACに4個目のトロフィーが届いたわけであります。

強さの秘密がどの辺にあるのかわかりませんが、受託サービス(主にキャンペーン)とゲーム、チャットサービス、その他自社サービスと多岐に渡るwebサービスを身近に感じられることや、3度優勝している人が身近にいるとか、まだまだ富豪的アプローチでじゃんじゃんお金で解決しますというわけにはいかないこととか、なんだかワチャワチャしているところが強さの要因かもしれません。

 

最後になりますが、取り急ぎ gistに最終的なコードを張っておきますね。

ISUCON7 予選1日目を1位で通過しました。


大会運営者の皆様ありがとうございました。 本戦もよろしくお願いいたします。

まとめ

決勝に行くことはできたけど、打倒組長は達成できず...

  • 勝因: /icons/*/fetch をなんとかできたこと
  • 敗因: nginxを2台にしていたこととswap対策できなかったこと

参加経緯

ISUCONの存在は同僚の組長が無双していたこともあり最初から当然知っていて、 自分も出たら結構いいところまでいけそうだなあみたいな気持ちはあったのだが、 惨敗したときの恐怖が勝って、あれこれ言い訳をして出てこなかった。

今年も、本番当日がつくばマラソンかぶっているので「参加できないわー残念!!」と言いながら心のなかでホッとしていたけど、 うっかりつくばマラソンのエントリーをし忘れてしまい、本番当日が空いてしまったということで言い訳ができなくなってしまった。。。

そんななかここ2年くらい業務で一緒にやっているGoの実装がクソ早い @mizukei が新卒の@jet_zousanと残り1名のメンバーを探しているという情報をつかんだので、それに食いついたというのが参加した経緯。

結果的にかなりいいメンバー構成でチームが組めてラッキーだった。

チーム編成

  • @jet_zousan 弊社インフラの期待の新人 あの組長の弟子
    • インフラを一任
    • 最初は環境構築を担当する
  • @mizkei11 Goを愛しGoに愛された男
    • アプリ担当(主にGoの言語依存の問題を潰す)
    • 最初はコードリーディングを担当する
  • @ken39arg 10年戦士
    • アプリ担当(主にDBの使い方の問題を潰す)
    • 最初はslowlogの解析とIndexチューニングを担当する

とにかく自分と @mizkei11 はコードを書くのが早いというのが強み

インフラ面について@jet_zousanの知識は申し分ないけど業務経験が少ないのが不安要素 (経験的に俺がインフラ補えれば良いのですが最近さっぱりで…)

そういうチームなんで、とにかく当たり前の事を当たり前に積み上げて、手数でぶん殴ろうというのが唯一の作戦。

事前準備

  • チーム組んだ瞬間に Slackとgithubのprivate repoを作った
  • 1週間前に集まって、決勝に絶対に行くこと、打倒組長を誓いあった
  • issueにsnipetsとTODOを各自書き込んでおいた
  • infra担当の @jet_zousan がなんかchefで準備してた(よくわからん)
  • 自分たちのいつものフィールドで戦うことを決めておいた(nginx,golang,mysql,redis)
  • apache→nginxやpostgresqlmysqlの移行ブログなどのリンクをブクマ

当日競技前

開始が遅れたので、作戦とマニュアルの確認をクドいほど行った。

  1. ホワイトボードに「おちつく」とでかく書いた
  2. ホワイトボードに「チャレンジしない」とでかく書いた
  3. 事前に決めたチェックポイントでタイマーのアラートを設定した。内容は記憶が曖昧だがこんな感じ
    • 30分 達成指標
      • いじるファイルのgit管理
      • alp用nginx-logの設定
      • slowlogの設定
      • アプリをgoに切り替える
      • 最初のベンチが終了
      • pt-query-digestとalpでボトルネックを立て作戦を立て直す
    • 2時間 達成指標
      • 自分たちの当たり前を終わらせる
        • Index
        • N+1を解消
        • 言語依存の基本的な改善
    • 7時間 達成指標
      • 基本これより先はいじらない
      • 再起動テストなどを行う

競技開始

13:13 ~ 14:15頃

  • まず全員で当日マニュアルを読み込んだ。
  • とりあえず1人1台サーバーに入り構成を確認
  • nginxの設定を手で書き換えてalpで集計できるようにlogformatを変更
  • mysqlはクライアントでログインしset globalでslowlogを出力するように変更
  • systemdでアプリをgoに切り替える
  • 1回目ベンチ
  • githubにgoのコードをコミット
  • alp, pt-query-digestを実行
  • pprofを仕込む
  • buildを1台でできるようにする

ここまでで当初の想定より30分ほど送れていたけど、「おちつこう」と声を掛け合って冷静になれていた

ここで、/icons/をどうにかしないとslowlogすらまともに見れないという事になり、一度立ち上がって作戦会議

(ちなみにこの後最後までpt-query-digest は見れなかった...)

たてた作戦

下記をそれぞれサクッとやってしまおうということになる

  • @mizkei11
    • 画像を静的ファイルに保存する
    • 初期画像を取り出す
  • @jet_zousan
    • DBサーバーでアプリを動かし各appサーバーからPOST /profileGET /icons をDBサーバーに回し画像サーバーを1台にする
  • @ken39arg
    • slowlogがまともに見れないのでコードリーディングをして自明なIndexを張る

14:15頃〜19:30

僕に割り振ったMySQLにIndexは、必要なのが少なくてすぐに貼り終えてしまった(messageとimageのみ)

暇になったのでやることリストを確認しながら静的ファイルをgzにしgzip_staicで配信するなどをやった。

そうこうするうちに、15:30くらいに画像の静的ファイル化が完了してスコアが上がりだしたと思う。

ぼくは簡単に出来そうな最適化が見当たらないので/fetch の修正に取り掛かることにした。

この時たしか16時前で17時までにキメるぜ!と宣言して16:30くらいに書き終えた。

ただそこから、くだらないタイポとかでなんやかんやと時間が過ぎて結局投入したのが17時過ぎになってしまった。(今回僕に限らず3人とも普段しないようなくだらないミスが目立った。パスを間違えたり、タイポしたり、、、)

ここでスコアが一段階跳ねたところで、インフラ担当の@jet_zousanが、cacheヘッダーを入れたら、スコアが爆上げして、超盛り上がった。

この結果、DBサーバーが完全なるボトルネックになって、isu1と2でtry_file&proxyでプロキシしあう構成(通称クロス)に移行するが、またしてもくだらない設定ミスとかでなかなかすんなりいかない時間を過ごしてしまい実投入は少し先になった。

nginxの設定に手間取っている間に jsonyfy みたいな名前でに隠された /messages/history のループクエリの解消が@mizkei11 により投入されて確かトップにたったと思う。

そしてクロス構成が決まったところで46マンまで伸びて2位以下とダブルスコアがついてきたので、予選突破の安全策を取りに行くことにした。

そう、僕たちの作戦は「チャレンジしない」

19:30

ここまで手をつけていなかったmysqlのチューニングに取り掛かろうととりあえずinnodb_buffer-pool-sizeの調整をしたところ、スコアが半分くらいになってしまった。

さらにベンチを回すとさらに半分くらいに。。。

ここで設定を戻したりしたが、一向によくならない。

何をしたのか振り返るが、よりにもよってmysqlの設定に関してのみコミットしておらずdiffが不明。

絶対にinnodb-buffer-pool-sizeしかいじってないとのことだが@jet_zousanを信じきれずDBサーバーばかりをガン見するという状態がしばらく続いたところで、ようやくアプリがswapしていることに気づいた。

画像ファイルの名前を画像ののsha1で生成していたのだが、readallで画像を一度に読み込んでいたのだ。

よく考えてみると、今までベンチを回す時は必ずコード修正後でアプリをビルドし直していたからメモリが解放されていたのだが、DBのチューニングの時はDBしか再起動しなかったため、メモリを食いつぶしたままのアプリでベンチがかかっていたため性能が著しく劣化してしまったと予想。

ようやくそこに気づいたので、アプリを再起動すれば少なくとも30万はスコアが出るということが確認できたので、再起動後はちゃんとスコアが出るだろうと判断し、対策しないことにした。

20時の時点で30万まで行っていたのはチームは他にいなかったので3位以内には入れるという判断だった。

そういうわけで、余計なことはやめておこうと、ログもそのまま、欲を出さずに20:30には競技をやめて反省モードに入った。

結果として、この判断は正しく、ベストスコアとの隔たりは大きかったものの1日目1位をキープしたまま決勝の切符を手に入れることができた。

めでたしめでたし。

感想

判断は正しかった。ただ、本当にいつも通りできていたらあと2手くらいは打てたし、冷静になっていたら最後チャレンジではなく当たり前の判断でswapの解消ができたと思う。

いや、そもそもモニタリングしていたらもっとはるかに早くswapに気づけたはず、、、

決勝は同じミスをしないように、そして決勝はチャレンジします。勝つだけなので

10月のトレーニング計画

1/28の館山若潮マラソンに出ることにしました。

そこでサブスリー達成します!

 

さて、そのための練習ですが4カ月前の10月はポイント練は週一くらいにして、無理せずしっかりと距離を踏んでいこうと思います。

ここ1カ月はダラダラ過ごしましたが、それでも心拍はスイムで鍛えてたので4ヶ月前としては問題ないはずです。

 

10月の基本メニュー

 

月 : スイム (ジムのマスターズクラス) 3km弱

火 : ラン (帰宅コース) 15km

水 : ヨガ&バイク(片道15km×2)

木 : ラン (帰宅コース) 15km

金 : スイム (ジムのマスターズクラス) 3km弱

土 : ラン 20km 又は 3時間走

日 : off (土曜日と入れ替わりあり)

 

平日のランは基本スピードを気にせず気分で走る。

速く走りたい気分なら速く走るしのんびりでもOK

 

ポイント練は週末朝で距離を踏む系の練習LSDな感じで、11月にスピード練習できる下地を作る。

ただ、もしスピード練習する気分になったらその時はやる。

とにかく気分を優先する。

 

あまり月間距離は気にしないことにしているけど、計算上は200kmくらい走るはず。

量より質派だけど、怪我しないようにここで下地をしっかり作るのが10月のノルマ。

 

11月から1月前半ははインターバルとペース走のポイント練二本立てに切り替える予定。

 

達成できると良いけど明日は早速雨予報??

 

湘南OWS

一昨日、湘南OWS10kmに参加した。

 

この大会にエントリーしたのは2回目で1回目は5年前。

その時はまあまあくらいの練習で、完泳を目標にしてたんだけど、1ヶ月前にビーチフットっていう砂浜でやるラグビーをしてハムストリングスの肉離れをしてDNFとなった。

 

それからずっと頭に引っかかってはいたんだけど、水泳の練習はどんどん減り、子供も生まれてさらに減りぜんぜん泳がない日々が続いていた。

 

それからしばらくして、紆余曲折あってランニングにハマりマラソンでサブスリーを目指すようになった昨年、夏場の練習の一環としてまた泳ぐようになり、たまたまfacebookで目に付いた富津アクアスロンに申し込んだ。

それが沼の入り口だった。

 

小学生の頃、陸のスポーツは全然ダメだけど泳ぐのだけは得意で僕の支えだった水泳。

中学生では水泳部で市の大会で入賞もした水泳。

大好きだった水泳の新しい楽しみ方というか35歳でも成長出来る伸び代に出会ってしまった。

 

海で泳ぐのは楽しい。そして、また早くなる喜びを感じることができる。

もっと早くなりたい、逗子から江ノ島まで泳ぎたい。頭の中はそんな気持ちでいっぱいになった。

 

夏は海、冬は陸、どちらもサブスリーを達成したくなった。

そして今年の目標を水陸でのダブルサブスリー達成と決めた。

 

1月のフルマラソンは3時間8分あと少し足りなかった。でもスイムがあるから切り替えて、週に2回1時間で3000m泳ぐキツイメニューを組んでPDCAを回しながら追い込んだ。

追い込みすぎて声が出ちゃうので、マスターズクラスのおばちゃん達に笑われた。

 

この夏、2つの大会と1つの練習会、長水路での練習、海での自主トレと家族にわがままを言って週末の午前中を何度も使って準備をした。

2週間前の5kmの大会では1時間21分で泳げた。

フルマラソンでサブスリーを狙うならハーフのタイムとしては十分なタイムだ。

俺の中で海でのサブスリーはほぼ確定していた。

 

湘南OWSの前日は事前登録もあったし、万全を期すため仕事を休み21時には寝た。

朝は4時に起きうどんを食べ、1時間おきに炭水化物をとり、30分おきにOS1を飲みローディングした。

体調は万全、天候も良好。最高のスタートを迎えた。

 

500人が一斉にスタートするというのは初めてでだったけどバトルの激しさが桁違いだった。

ドラフティングで後ろにつかれるのはものすごいストレスだった。

うねりも大きかった。何度も人が上から降ってきた。ゴーグルも蹴られた。OWSの洗礼を受けてる気分だった。

 

逗子の海を一周まわった頃、船に乗る沢山のスイマー達を見た気がした。

3kmのわけないし1km?多すぎないか?いや気のせいだろ?そんな感じ。

3km地点で初めての給水をした。どんな船が待ってるんだろう?どうやって飲むんだろう?ちゃんと見つけられるだろうか?と不安でいっぱいだったけど、大きなゴムボートが二台あって同じキャップの人達が群がってたのですぐにわかった。

ボランティアのおばちゃん達に水をもらった。3kmは水だけと聞いていたけどエネルゲンのゼリーももらえた。ボートにつかまりながらしっかり飲んで離れる時「59分」という声が聞こえた気がした。

 

???

 

そんなわけないよな?

いくら何でも3kmにそんなかかるわけないよね?しかも俺は絶対に比較的前の集団にいるはずだ。ペースも悪くないし。

頭の中がモヤモヤした。

ブイは全く見えなかった。

波が高すぎクロールの流れのヘッドアップでは全然見つからない。ガードの方達のシャツを目印にしたが左右にいて結構幅があった。

それにそれすらもすぐに見失う感じだった。

 

思い返せば違和感は他にもあった。

すぐ横にいた人との距離が一瞬にして大きく離れたり、誰もいなかったはずなのに急に真横にいたり。多分うねりに流されてたんだと思う。

 

目印のブイや船は見つからないけど、他のスイマーやガードを頼りに進んでいくと突然バンと現れる。現れるとそこに向かって安心して進めるので心が休まる。それを4回繰り返して5kmの給水にたどり着いた。

今度は中学生くらいの子供のボランティアが水とエネルゲンをくれた。

思い切ってモヤモヤを聞いてみた。

 

「今何時?」

 

「10時40分くらいです」

 

ヤバい!マジだった。

何でこうなったのかわからないけど、間違いなく足切りの当落線上にいることを理解した。

ウソだ!と思った。

ただ泳ぎを止められることもなかったので前に突き進んだ。

ここからは江ノ島がはっきり見えるようになったので、ブイを追うのを諦めて江ノ島灯台をヘッドアップで確認しながら泳ぐことにした。

 

固定の目標物が出来たので、うねりで体がずらされてることにもようやく気づいた。

人ではなく灯台。強いうねりが来たらすぐヘッドアップで修正するようにした。

 

7km地点一緒に給水した人が、タイムアップまでどのくらいか聞いていた。

現実を受け入れたけど答えを聞きたくなくて、すかさずボートを離れた。

シックスビートにしようとしたけど足は攣りかけてて無理だった。

 

うねりは相変わらずだった。

江ノ島灯台を目指す作戦はここからは失敗だった。

距離が近くなるとその方向は進路から左にずらてしまうようだった。

何度も平泳ぎをする羽目になった。

腕もきつくなってきて何人かに抜かれた。悔しいけどついていけなかった。

 

9kmのボートの前に小さめのオレンジブイがあった。

最終ブイと勘違いして右肩で回って陸を目指したが、感覚的におかしくてものすごく不安になった。次のブイもゴール見えない。

ここに来てどこにいけばいいのか全然わからなくなった。

結論から言うとそのブイは最後の部位ではなかった。その先に黄色い四角ブイがあった。

 

最後の力を振り絞って陸を目指したがゴールは全然わからなかった。

ゴールよりだいぶ離れたところに上陸して結構走った。

 

どうやら制限時間内にゴールはできたようだった。

タイムが気になった。3時間24分。

しばらく受け入れることができなかった。

 

受け入れることができなくて、完泳者の数が少ない事にしばらく気づかなかった。

 

facebookには悔しい!と書き込んだ。

 

さて、冷静になってリザルトを見ると完泳者の数が異常に少ない事に気付いた。

facebookTwitter、インスタなんかを見て何が起きたのか把握した。

 

今年の湘南OWSは大波乱だったようだ。

まず完泳した事に胸を張ろうと思う。

 

そして、もしかすると5kmはギリギリすぎていたのかもしれない。でもあまりの状況に見逃してもらえたのかもしれない。

そう考えると、考えな甘さ未熟さ力不足にまた悔しくなった。

 

本当はOWSはこれっきりにすることも考えていた。

だって練習キツイから。

 

でもやりますよ。

必ずやりますよ。

来年こそサブスリーそのためにこれからも苦しい練習をします。

マラソンもやりますよ。二刀流になるけど来年はこの悔しさをバネにダブルサブスリー達成します。

 

FacebookはOWSばかりでキモいしTwitterもキモいしで、でもどこかにこの気持ちをぶつけたくて未使用だったここに書いたら、ずいぶん長くなってしまった。

2014年1月1日

あけましておめでとうございます。

2011年以来書いて無かったですが、今年はブログを書こうと思います。

というわけで2014年の目標は

1. ブログを書く
2. 痩せる(見た目に)

の二つです。

仕事の目標はありません。
今年は流れに身を任せて人のためになろうと思います。
なのでせめてブログ書きます。

腹がまた出てきました。
何度目のあれかわかりませんが、自転車乗ってますけど食べ過ぎてて太ってきてしまいました。
食べる量を減らして筋肉も少し鍛えます。



さて、月が変わったので、2014年1月の目標は

1. 昨年2月頃に携帯アドレスが変わったことを皆様に謝りつつ連絡する

の一つです。

昨年の初めに携帯をドコモから、auに変えたので、所謂メアドが変わったのですがメアド変わりましたメールを誰にも送っていませんでした。
これはそのうちと思って今日にいたったわけです。
結果あけおめメールが一件も来ませんでした。
教えてても来たかわかりませんがちゃんと連絡します。

以上。