メインコンテンツにスキップ
  1. ホーム
  2. ガイド
  3. 予定表と住所録を、全部入りのNextcloudから軽いRadicaleへ切り出す

予定表と住所録を、全部入りのNextcloudから軽いRadicaleへ切り出す

予定236件・連絡先575件をNextcloudからRadicaleへ移した記録。データは.ics/.vcfの素のファイルになり速くなったが、Web画面と共有機能は失う。選ぶ前に知るべき制約として「Radicaleは重いREPORT問い合わせを同時に受けられない(並列5本で全滅、直列なら1本4.7秒)」という実測も書いた。

公開日:
2026-10-05

前の記事で、連絡先とカレンダーをiCloud・GoogleからNextcloudへ移した。それを今度はNextcloudから引き剥がして、Radicaleという「予定表と住所録しかできない」小さなサーバーへ移した記録。

先に正直に書くと、Nextcloudで困っていたことは何も無かった。故障の修理ではなく、作りを良くするための工事だ。だから得たものと同じ分量で、失ったものを書く。

前提

  • 常時起動しているLinux環境とDocker(ソフトを1つずつ箱に入れて動かす仕組み)が使えること。土台は自分の「サーバー」を持つとはどういうことか
  • すでにNextcloudの連絡先・カレンダーを使っている状態(前の記事)
  • CalDAV(予定表をやりとりするための標準的な通信規約)・CardDAV(住所録用の同じもの)の名前だけ知っていれば十分

動かした理由は「規模が不釣り合い」の一点

移す前に、Nextcloud側の中身を?exportで実測した。住所録575件、予定表236件、そして空の予定表が2つ(片方はiPhoneのリマインダーが勝手に作ったもの)。

🔴 前の記事に書いた「連絡先633件・予定232件」は誤りだった。移した件数は、移した日に書き留めただけでは当てにならない。

実データの合計は約230KB。テキストファイル1枚に収まる量だ。これに対してNextcloudはPHPで書かれた大きな母屋にデータベースまで抱えている。やっている仕事に対して仕掛けが大きすぎる、というのが唯一の動機だった。

Radicaleは何で、何ではないか

RadicaleはCalDAV/CardDAVだけを話す小さなサーバー。データベースを持たず、予定1件が.icsファイル1枚、連絡先1件が.vcfファイル1枚として、普通のフォルダにただ並んでいるだけ。

やっているのは「フォルダのテキストファイルを、CalDAVという会話の形に翻訳して端末に見せる」中継だけだ。Appleのカレンダーアプリには「このフォルダを見ろ」という設定が存在せず、CalDAVでしか話せないので、この翻訳役が必要になる。あとは各予定にetag(版番号)を付けて差分と衝突を管理するだけ。

何ではないかのほうが重要だと思う。

  • 使えるWeb画面が無い。ブラウザで開いても予定を見る画面は出てこない。どうしても画面が欲しければ、CalDAVを外から繋げられるメールサービス(自分はFastmail)を窓口にする手はある。
  • 共有リンクも共有のUIも無い。「この予定表をあの人に見せる」をボタンで作れない。
  • ファイル置き場ではない。PDFもWordも置けない。契約書の保管はNextcloudに残した。
  • 何でも1箇所にある便利さを捨てる。サーバーが1つ増え、端末のアカウントも1つ増える。

代わりに得たのは、速さ、攻撃される面の小ささ(PHPの大きな母屋とデータベースが無くなる)、そしてバックアップがフォルダのコピーで済み、ソフトが壊れても中身はテキストで読めること。この3つのために上の4つを捨てた。判断は人によって逆でいい。

立てて流し込む

構成はdocker composeで1つ、tomsquest/docker-radicale(Radicale 3.8.1)。127.0.0.1にだけ口を開け、外から直接は触れないようにした。認証はhtpasswd(ユーザー名とパスワードの対応表ファイル)だけ。設定の書き方は版で変わるので公式ドキュメントのConfigurationの項を見てほしい。

移行前にNextcloudから控えを取る。

# 住所録
curl -u "<ユーザー名>:<アプリパスワード>" \
  "https://drive.example.com/remote.php/dav/addressbooks/users/<ユーザー名>/contacts?export"
# 予定表
curl -u "<ユーザー名>:<アプリパスワード>" \
  "https://drive.example.com/remote.php/dav/calendars/<ユーザー名>/<予定表名>?export"

入れ物の一覧は同じURLに-X PROPFIND -H "Depth: 1"を付けて取る。流し込みはスクリプトを書いた。設計で効いた点が2つ。

  • ファイル名をデータの中のUID(1件ごとの識別子)にする。何度流し直しても同じファイルを上書きするだけで、件数が増えない。
  • 繰り返す予定の「例外日」(毎週の予定のうち1回だけ時間をずらした分)は同じUIDを持つので、1ファイルにまとめる。別ファイルにすると予定が二重になる。

結果は575件・236件でNextcloud側と完全一致、失敗0件。

🔴 立てるときに踏んだ穴

  • コンテナの中のRadicaleは自分と違うユーザーで動く。自分の場合は数字のユーザーID 2999だった。パスワード表をrootの持ち物にしたままだとPermission deniedで起動しない。そのIDに所有者を変え、読み取りだけ許すのが正解。
  • 🔴 「正常な応答コード」を3回続けて読み違えた。トップページのGETは302(転送)が正常で401ではない。予定表フォルダのGETは認証が通っていても403が仕様どおりで、壊れていない。正しい確認はPROPFINDを投げて207が返ることで、件数もこれで数える。

端末の繋ぎ直しに「移行」は無い

ここが一番地味で、一番時間がかかる。サーバー間でデータを移しても、端末のアカウント設定は1台も自動で付いてこない。全部手で繋ぎ直す。 Apple(Mac・iPhone)で詰まる点は1つだけで、はっきりしている。

  • 🔴 アカウント種別の「自動(Automatic)」では繋がらない。Appleは.well-known/caldavという決まった入口で「本当の場所はどこ?」と尋ねるが、Radicaleはここでトップページへ転送するだけで場所を教えない。
  • 必ず「手動(Manual)」を選ぶ。サーバーアドレスはホスト名(cal.example.com)、駄目なら自分のフォルダまでのフルパス(https://cal.example.com/<ユーザー名>/)を直接入れる。前の記事と同じ系統で、自動検出は当てにしない。

AndroidとThunderbirdは今回つないでいないので手順を書かない。確かなのは、Androidの標準カレンダーにはCalDAVアカウントを足す項目が無く、別途CalDAVクライアントのアプリを入れて端末のカレンダーに同期させる形になること、ThunderbirdはCalDAVに標準対応していることだけだ。

並行期間(両方に同じデータがある状態)の事故防止で効いたこと。

  • 古い側の表示名を「OLD(使わない)」に変える。同じ名前が2つ並ぶと、どちらを編集しているか分からなくなる。
  • 端末側で新規作成先の既定のカレンダーを新しい方に変える(Macは Calendar > Settings > General、iPhoneは 設定 > アプリ > カレンダー)。ここを変えないと、自分で作った予定だけ古い方に入り続ける。
  • 古い側を消すのは、①手元に控えがある②新しい側の件数が一致している、の両方を実測してから。
  • 🔴 Nextcloudの予定表は204が返っても消えていない。ごみ箱行き(論理削除)になり、一覧に出続けて中身も読める。完全に消すには削除要求にX-NC-CalDAV-No-Trashbin: 1を付ける。住所録はごみ箱が無く一発で消えるので、挙動が揃っていない。

🔴 Radicaleは重いREPORT問い合わせを同時に受けられない

Radicaleを選ぶ前に知っておくべき制約で、しかもほとんどどこにも書かれていない。自分は後から実測で踏んだ。

REPORTは、カレンダーアプリが「この日からこの日までに何が入っているか教えて」と尋ねるCalDAVの問い合わせだ。Radicaleはこれを受けるとフォルダの中の全ファイルを毎回走査する。繰り返す予定は1ファイルにルールが1行入っているだけなので、「表示を1年先までに絞る」といった対策は効かない。遅さを決めているのは問い合わせの範囲ではなく、フォルダ内のファイル総数だ。

予定表が現在2,626件まで増えた結果、1回の範囲問い合わせに約4.7秒かかる。これ自体は待てる。問題は同時に投げたときだった。

  • 事前読み込みを直列3本から並列5本に変えたら、30秒で5本とも通信切れになり、取れた予定は0件になった。
  • 直列に戻したら、5期間228件が23.3秒で全部成功した(1本あたり約4.7秒)。

つまりRadicaleは重いREPORTを並べて投げられると、速くなるどころか全部落ちる。「カレンダーアプリを2つ同時に開く」「月を素早く切り替える」「複数の範囲を先読みする自作ツール」のどれでも起こり得る。付き合い方は、どれも「並列化しない」方向になる。

  1. 1本ずつ流す列を作る。手前のサーバーに順番待ちの列を1本入れ、範囲問い合わせは必ず直列で流す。
  2. 同じ範囲の同時要求は1本に相乗りさせる。2箇所から同じ月を聞かれたら投げるのは1回で、答えを2箇所に配る。画面の操作から来た要求だけ列の先頭に割り込ませると、裏の先読みが詰まっていても人は待たされない。
  3. 一度取ったら捨てない。1件保存・削除するたびに全期間のキャッシュを丸ごと捨てて再取得していたのが最悪の形だった。変わった1件だけを手元で差し替えるようにしたら、保存のたびの5秒が消えた。
  4. 範囲は広めに取って一度だけ聞く。細かく刻んで何度も聞くほうが遅い。

⭐ 一般則。相手が1本ずつしか捌けない所へ、並べて投げてはいけない。速くする手は並列化ではなく、①同じものを二度聞かない ②要るものだけ聞く、の2つしかない。

時間帯は最初に片付ける

2人で共有するカレンダーを、互いに別の国にいる状態で使っている。時間帯(タイムゾーン)の扱いは後から足す機能ではなく、土台だった。救われたのは、保存の形が最初から正しかったことだ。.icsの中では開始時刻がDTSTART;TZID=Asia/Singapore:...のように**「どの地域の時計で何時か」の形で書かれている**ので、絶対の時刻として一意に決まる。Apple純正のカレンダーはこれを読む側の端末の時間帯で出し直してくれる。標準形式に素直に保存されていれば、時間帯は自動で解決する。 壊れたのは自作の画面のほうで、そこから学んだのは次の3つ。

  • 変換は入口と出口の2箇所だけでやる。画面に出す直前と、入力を受け取った直後。途中の計算に混ぜると、どこが変換済みか分からなくなって必ず二重変換が起きる。
  • 「いまどの時間帯で見ているか」を常に画面に出す。Google・Apple・Thunderbirdのどれもこうしている。あとから読まれる文章に時刻を書くときも、必ず時間帯を添える(保存された文は読む人の時間帯に書き直せない)。
  • 時刻の変換は、画面で確かめる前に机上で全パターン流す。夏時間の切り替わりの前後、30分ずれる地域、日付をまたぐ西側の地域は、実機を1件ずつ見ても踏めない。

バックアップは単純になった

データが素のファイルになったので、バックアップはフォルダを同期するだけになった。既存の日次スクリプトに2行足して終わりで、初回送信は1,035ファイル・379KiBで32秒。ただし消されても戻せる形にしておくことは、データが素のファイルになっても別途必要で、そこは変わらない(バックアップを「消されても戻せる」形にする)。

結果

575件・236件の移行は失敗0で完了し、MacとiPhoneから繋がっている。サーバー上にあるのは1,000枚ほどのテキストファイルで、catで開けば中身が読める。

ファイル置き場のNextcloudは据え置いた。素のファイルを普通のフォルダ構造で保存しているという一点では、調べた移行先候補よりNextcloudのほうが上だったからだ。目的が「データを読める形に保つ」なのに、手段がそこを後退させるなら却下する。

更新履歴 — 2026-10-05: 初版