Nextcloud Object Storage

Siehe auch

Verwandte Artikel
Offizielle Dokumentation

Nextcloud unterstützt neben dem klassischen Dateisystem auch Object Storage als Primary Storage - also den Ort, an dem die Datei-Inhalte selbst liegen. Metadaten (Dateibaum, Rechte, Shares) bleiben in der SQL-Datenbank. Vorteil: praktisch beliebige Skalierung bis in den Petabyte-Bereich, typische Cloud-Preisstruktur, einfaches Backup auf Object-Store-Ebene. Nachteil: der Dateibaum ist ohne Nextcloud nicht mehr direkt lesbar (die Files liegen unter Keys wie urn:oid:12345 im Bucket), und einige wenige Features sind im Object-Modus komplizierter (z.B. Preview-Dateien).

Nextcloud 33 unterstützt diese Backends:

  • Amazon S3 und S3-kompatible Stores (Ceph RadosGW, Minio, Wasabi, Infomaniak, etc.) - Klasse \OC\Files\ObjectStore\S3

  • OpenStack Swift (API v2 und v3) - Klasse \OC\Files\ObjectStore\Swift

  • Microsoft Azure Blob Storage - Klasse \OC\Files\ObjectStore\Azure

Begriffe

Bucket / Container

Oberste Organisationseinheit im Object Store. Bei S3 „Bucket“, bei Swift „Container“. Für Nextcloud jeweils genau eines pro Instanz; weitere Buckets lassen sich über Multibucket-Konfiguration anbinden.

MountProvider

Nextcloud-interne Abstraktion, die entscheidet, wo die Dateien eines Benutzers liegen. Für Local Storage ist das OC\Files\Mount\LocalHomeMountProvider, für Object Storage OC\Files\Mount\ObjectHomeMountProvider. Die aktive Variante steht in der Tabelle oc_mounts.

Object Key

Identifier einer Datei im Bucket. Nextcloud verwendet das Schema urn:oid:<fileid>, wobei fileid direkt aus oc_filecache.fileid stammt. Der ursprüngliche Dateiname wird dabei nicht als Key benutzt, der lebt nur in der Datenbank.

Primary Storage

Ort, an dem Nextcloud die eigentlichen Datei-Inhalte ablegt. Steht in config.php unter objectstore (bzw. ist nicht gesetzt für Local Storage).

Nextcloud von Beginn an mit Object Storage aufsetzen

Der sauberste Weg: vor dem occ maintenance:install-Aufruf die Object-Storage-Konfiguration als Partial-Config neben config.php ablegen. Der Installer führt beide Dateien zusammen, und der Object Store wird zum Primary Storage. Im Beispiel mit S3 gegen Infomaniak:

/var/www/html/nextcloud/config/objectstore.config.php
<?php
$CONFIG = array (
    'objectstore' => [
        'class' => 'OC\\Files\\ObjectStore\\S3',
        'arguments' => [
            'autocreate'     => true,
            'bucket'         => 'bucket-name',
            'hostname'       => 's3.pub1.infomaniak.cloud',
            'key'            => '7ca40b8a-2572-4ff7-9a9a-57051bf33dc7',
            'region'         => 'us-east-1',
            'secret'         => '51c9d8b3-25f7-45dc-812f-2146e38d2167',
            'use_ssl'        => true,
            'use_path_style' => true,
        ],
    ],
);
sudo -u apache php /var/www/html/nextcloud/occ maintenance:install \
    --admin-pass 'linuxfabrik' \
    --admin-user 'nextcloud-admin' \
    --data-dir   '/data' \
    --database      'mysql' \
    --database-host 'localhost' \
    --database-name 'nextcloud' \
    --database-user 'nextcloud' \
    --database-pass 'linuxfabrik'

Migration Local Storage zu Object Storage

Migration eines bestehenden Block-Storage-Setups zu S3 oder Swift. Das Vorgehen ist bei beiden Backends identisch; unterschiedlich sind nur die Kopiertools und die abschliessende Nextcloud-Konfiguration. Die Auswahl des Kopier-Tools (aws oder rclone) spielt keine Rolle, das Ergebnis ist dasselbe.

Bemerkung

Die Zeitangaben im Beispiel stammen von einem System mit rund 90 Benutzern, etwas mehr als 2.1 Millionen Dateien und einer Gesamtgrösse von rund 4 TB.

Achtung

Vor der Migration unbedingt ein Datenbank-Backup erstellen. Die Schritte ab „Nextcloud auf Object Storage umkonfigurieren“ sind nicht trivial rückgängig zu machen.

Voraussetzungen

  • Datenbank heisst nextcloud.

  • Lokaler Nextcloud-Storage liegt auf /var/www/html/nextcloud/data/.

  • Object Store (S3-Bucket oder Swift-Container) mit dem Namen bucket-name ist angelegt, Credentials liegen vor.

  • Im Beispiel wird gegen Infomaniak im Zürcher Datacenter migriert: S3-Endpoint https://s3.pub1.infomaniak.cloud, Swift-Endpoint https://api.pub1.infomaniak.cloud/identity/v3.

Vorbereitung

# 30 minutes
chown -R apache:apache /var/www/html/nextcloud/data/
# 30 minutes
sudo -u apache /var/www/html/nextcloud/occ files:scan --all

Maintenance-Mode aktivieren (sperrt Benutzer für die Dauer der Migration aus und hält den Datenbestand konsistent):

sudo -u apache php /var/www/html/nextcloud/occ maintenance:mode --on
systemctl restart php-fpm

Aus der Datenbank eine Liste aller Benutzer-Dateien und Metadaten-Dateien exportieren (die beiden Dumps zusammen rund 500 MB im Beispiel):

mysql_user='root'

# user files
mariadb --user="$mysql_user" --password --batch --disable-column-names --database=nextcloud << 'EOF' > /tmp/user_file_list
    SELECT CONCAT('urn:oid:', fileid, ' ', '/var/www/html/nextcloud/data/', SUBSTRING(id FROM 7), '/', path)
    FROM oc_filecache JOIN oc_storages ON storage = numeric_id
    WHERE id LIKE 'home::%'
    ORDER BY id;
EOF

# meta files
mariadb --user="$mysql_user" --password --batch --disable-column-names --database=nextcloud << 'EOF' > /tmp/meta_file_list
    SELECT CONCAT('urn:oid:', fileid, ' ', SUBSTRING(id FROM 8), path)
    FROM oc_filecache JOIN oc_storages ON storage = numeric_id
    WHERE id LIKE 'local::%'
    ORDER BY id;
EOF

Sync per aws (S3)

Das Python-basierte AWS CLI installieren (die Version in den OS-Repos ist meist zu alt) und konfigurieren:

cd /tmp
dnf --assumeyes install unzip
curl --output awscliv2.zip https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip
unzip awscliv2.zip
./aws/install --bin-dir /usr/local/bin

mkdir /root/.aws
cat > /root/.aws/config << 'EOF'
[default]
region = us-east-1
s3 =
    max_concurrent_requests = 100
EOF

cat > /root/.aws/credentials << 'EOF'
[default]
aws_access_key_id = 7ca40b8a-2572-4ff7-9a9a-57051bf33dc7
aws_secret_access_key = 51c9d8b3-25f7-45dc-812f-2146e38d2167
EOF

Sync ausführen:

cd /var/www/html/nextcloud/data/objectstore_files

# ~75 MiB/s = 4.4 GiB/min = 263 GiB/h = 6.1 TiB/d
# first sync takes 14h 40min; a second sync a day later takes 1h 08min
aws s3 sync . s3://bucket-name --endpoint-url https://s3.pub1.infomaniak.cloud --color auto

Sync per rclone (Swift)

dnf --assumeyes install rclone   # from EPEL

cd /var/www/html/nextcloud/data/objectstore_files

export RCLONE_CONFIG_MYPROJECT_TYPE=swift
export RCLONE_CONFIG_MYPROJECT_ENV_AUTH=true
source path/to/openstack.rc

# ~25 MB/s
rclone sync --copy-links --progress . myproject:mybucket

Nach erfolgreichem Sync die Symlink-Staging-Area wieder entfernen:

rm --recursive --force /var/www/html/nextcloud/data/objectstore_files

Nextcloud auf Object Storage umkonfigurieren

Ab hier wird die laufende Instanz tatsächlich umgestellt. Datenbank-Backup zur Hand haben.

Datenbank-Einträge auf den Bucket umbiegen:

S3
cat > /tmp/update-database.sql << 'EOF'
UPDATE oc_storages
SET id = CONCAT('object::user:', SUBSTRING(id FROM 7))
WHERE id LIKE 'home::%';

UPDATE oc_storages
SET id = 'object::store:amazon::bucket-name'
WHERE id LIKE 'local::%';

UPDATE oc_mounts
SET mount_provider_class = 'OC\\Files\\Mount\\ObjectHomeMountProvider'
WHERE mount_provider_class = 'OC\\Files\\Mount\\LocalHomeMountProvider';
EOF

# a few milliseconds
mariadb --user=root --password --database=nextcloud < /tmp/update-database.sql

Maintenance-Mode deaktivieren und den Objectstore in config.php schreiben:

sudo -u apache php /var/www/html/nextcloud/occ maintenance:mode --off
systemctl restart php-fpm

cd /var/www/html/nextcloud/
sudo -u apache php occ config:system:set objectstore class                          --value='OC\Files\ObjectStore\S3'
sudo -u apache php occ config:system:set objectstore arguments autocreate     --type=boolean --value=true
sudo -u apache php occ config:system:set objectstore arguments bucket         --value=bucket-name
sudo -u apache php occ config:system:set objectstore arguments hostname       --value=s3.pub1.infomaniak.cloud
sudo -u apache php occ config:system:set objectstore arguments key            --value=7ca40b8a-2572-4ff7-9a9a-57051bf33dc7
sudo -u apache php occ config:system:set objectstore arguments port     --type=integer --value=443
sudo -u apache php occ config:system:set objectstore arguments region         --value=us-east-1
sudo -u apache php occ config:system:set objectstore arguments secret         --value=51c9d8b3-25f7-45dc-812f-2146e38d2167
sudo -u apache php occ config:system:set objectstore arguments use_path_style --type=boolean --value=true
sudo -u apache php occ config:system:set objectstore arguments use_ssl        --type=boolean --value=true

Hintergrund zum Storage-Umschreiben: https://github.com/nextcloud/server/issues/25781.

Swift
cat > /tmp/update-database.sql << 'EOF'
UPDATE oc_storages
SET id = CONCAT('object::user:', SUBSTRING(id FROM 7))
WHERE id LIKE 'home::%';

UPDATE oc_storages
SET id = 'object::store:bucket-name'
WHERE id LIKE 'local::%';

UPDATE oc_mounts
SET mount_provider_class = 'OC\\Files\\Mount\\ObjectHomeMountProvider'
WHERE mount_provider_class = 'OC\\Files\\Mount\\LocalHomeMountProvider';
EOF

mariadb --user=root --password --database=nextcloud < /tmp/update-database.sql
sudo -u apache php /var/www/html/nextcloud/occ maintenance:mode --off
systemctl restart php-fpm

cd /var/www/html/nextcloud/
sudo -u apache php occ config:system:set objectstore class                         --value='OC\Files\ObjectStore\Swift'
sudo -u apache php occ config:system:set objectstore arguments autocreate    --type=boolean --value=true
sudo -u apache php occ config:system:set objectstore arguments bucket            --value=bucket-name
sudo -u apache php occ config:system:set objectstore arguments region            --value=dc3-a
sudo -u apache php occ config:system:set objectstore arguments 'scope project domain name' --value=Default
sudo -u apache php occ config:system:set objectstore arguments 'scope project name'        --value=cabfeec2-ee88-4fba-8197-5bcae1ac96eb
sudo -u apache php occ config:system:set objectstore arguments serviceName       --value=swift
sudo -u apache php occ config:system:set objectstore arguments url               --value=https://api.pub1.infomaniak.cloud/identity/v3
sudo -u apache php occ config:system:set objectstore arguments 'user domain name' --value=Default
sudo -u apache php occ config:system:set objectstore arguments 'user name'       --value=f187e2b0-7d6c-419a-b5b0-9a5743677005
sudo -u apache php occ config:system:set objectstore arguments 'user password'   --value=linuxfabrik

# remove local OpenStack credentials file
rm --force path/to/openstack.rc

Abschluss

systemctl restart php-fpm
sudo -u apache /var/www/html/nextcloud/occ maintenance:repair

Anschliessend die Instanz testen: Upload, Download, Preview für Bilder und PDFs, Sharing, App-Funktionen.

Achtung

Kein Bild-Preview, Dateien lassen sich nicht laden, Logeinträge zu „Encryption“ oder „Seeking“?

Prüfen, ob die Tabelle oc_mounts wirklich aktualisiert und der LocalHomeMountProvider durchgängig auf ObjectHomeMountProvider umgestellt wurde.

Migration Object Storage zu Local Storage

Die Rückrichtung: von S3 oder Swift zurück auf Local Block Storage.

Voraussetzungen

  • Datenbank heisst nextcloud.

  • Nextcloud-Files liegen im Object Store in einem Bucket/Container namens bucket-name.

  • Der lokale Ziel-Datadir liegt unter $nextcloud_datadir (im Beispiel /data).

Vorbereitung

nextcloud_datadir='/data'
chown --recursive apache:apache "$nextcloud_datadir"
sudo -u apache /var/www/html/nextcloud/occ files:scan --all

Maintenance-Mode aktivieren, Background-Jobs stoppen:

sudo -u apache php /var/www/html/nextcloud/occ maintenance:mode --on
systemctl restart php-fpm
systemctl disable --now nextcloud-jobs.timer nextcloud-scan-files.timer

Datei-Listen aus der Datenbank

Je eine Liste für Verzeichnisse (mkdir-Input) und Dateien (mv-Input), getrennt für Benutzer- und Metadaten-Storage:

mysql_user='root'

# user directories
mariadb --user="$mysql_user" --password --batch --disable-column-names --database=nextcloud << 'EOF' > /tmp/user_file_mkdir_list
    SELECT CONCAT(SUBSTRING(oc_storages.id FROM 14), '/', path)
    FROM oc_filecache
    LEFT JOIN oc_storages  ON oc_filecache.storage  = oc_storages.numeric_id
    LEFT JOIN oc_mimetypes ON oc_filecache.mimetype = oc_mimetypes.id
    WHERE oc_storages.id LIKE 'object::user:%'
      AND oc_mimetypes.mimetype = 'httpd/unix-directory'
    ORDER BY oc_storages.id;
EOF

# user files
mariadb --user="$mysql_user" --password --batch --disable-column-names --database=nextcloud << 'EOF' > /tmp/user_file_mv_list
    SELECT CONCAT('urn:oid:', oc_filecache.fileid, ' ', SUBSTRING(oc_storages.id FROM 14), '/', path)
    FROM oc_filecache
    LEFT JOIN oc_storages  ON oc_filecache.storage  = oc_storages.numeric_id
    LEFT JOIN oc_mimetypes ON oc_filecache.mimetype = oc_mimetypes.id
    WHERE oc_storages.id LIKE 'object::user:%'
      AND oc_mimetypes.mimetype != 'httpd/unix-directory'
    ORDER BY oc_storages.id;
EOF

# meta directories
mariadb --user="$mysql_user" --password --batch --disable-column-names --database=nextcloud << 'EOF' > /tmp/meta_file_mkdir_list
    SELECT path
    FROM oc_filecache
    LEFT JOIN oc_storages  ON oc_filecache.storage  = oc_storages.numeric_id
    LEFT JOIN oc_mimetypes ON oc_filecache.mimetype = oc_mimetypes.id
    WHERE oc_storages.id LIKE 'object::store:%'
      AND oc_mimetypes.mimetype = 'httpd/unix-directory'
    ORDER BY oc_storages.id;
EOF

# meta files
mariadb --user="$mysql_user" --password --batch --disable-column-names --database=nextcloud << 'EOF' > /tmp/meta_file_mv_list
    SELECT CONCAT('urn:oid:', fileid, ' ', path)
    FROM oc_filecache
    LEFT JOIN oc_storages  ON oc_filecache.storage  = oc_storages.numeric_id
    LEFT JOIN oc_mimetypes ON oc_filecache.mimetype = oc_mimetypes.id
    WHERE oc_storages.id LIKE 'object::store:%'
      AND oc_mimetypes.mimetype != 'httpd/unix-directory'
    ORDER BY oc_storages.id;
EOF

Object-Store in ein lokales Staging-Verzeichnis ziehen

dnf --assumeyes install rclone   # from EPEL

nextcloud_datadir='/data'
mkdir --parents "$nextcloud_datadir/objectstore_files"
cd "$nextcloud_datadir/objectstore_files"

export RCLONE_CONFIG_MYPROJECT_TYPE=swift
export RCLONE_CONFIG_MYPROJECT_ENV_AUTH=true
source path/to/openstack.rc

rclone sync --progress myproject:mybucket .

# example timing:
# Transferred:      129.321 GiB / 129.321 GiB, 100%, 120.399 KiB/s, ETA 0s
# Checks:            101520 / 101520, 100%
# Transferred:       101537 / 101537, 100%
# Elapsed time:   2h30m56.7s

Dateien an den richtigen Ort verschieben

nextcloud_datadir='/data'
cd "$nextcloud_datadir/objectstore_files"

# user directories
while read -r folder ; do
    folder_path="$nextcloud_datadir/$folder"
    mkdir --parents "$folder_path" || echo "Error: Failed to create $folder_path!"
done < /tmp/user_file_mkdir_list | tee /tmp/user_file_mkdir.log

# user files
while read -r source target ; do
    if [ -z "$target" ]; then
        echo "Error: target for $source is empty!"
        continue
    fi
    target_path="$nextcloud_datadir/$target"
    \mv "$source" "$target_path" || echo "Error: Failed to move $source to $target_path!"
done < /tmp/user_file_mv_list | tee /tmp/user_file_mv.log

# meta directories
while read -r folder ; do
    folder_path="$nextcloud_datadir/$folder"
    mkdir --parents "$folder_path" || echo "Error: Failed to create $folder_path!"
done < /tmp/meta_file_mkdir_list | tee /tmp/meta_file_mkdir.log

# meta files
while read -r source target ; do
    if [ -z "$target" ]; then
        echo "Error: target for $source is empty!"
        continue
    fi
    target_path="$nextcloud_datadir/$target"
    \mv "$source" "$target_path" || echo "Error: Failed to move $source to $target_path!"
done < /tmp/meta_file_mv_list | tee /tmp/meta_file_mv.log

grep '^Error: ' /tmp/*.log

Das Staging-Verzeichnis sollte jetzt leer sein:

ls -l /data/objectstore_files/
rmdir /data/objectstore_files/

chown --recursive apache:apache "$nextcloud_datadir"
restorecon -Fvr "$nextcloud_datadir"

Datenbank auf Local Storage umbiegen

old_objectstore_bucket_name='mybucket'
nextcloud_datadir='/data'

cat > /tmp/update-database.sql << EOF
UPDATE oc_storages
SET id = CONCAT('home::', SUBSTRING(oc_storages.id FROM 14))
WHERE id LIKE 'object::user:%';

UPDATE oc_storages
SET id = 'local::$nextcloud_datadir/'
WHERE id = 'object::store:$old_objectstore_bucket_name';

UPDATE oc_mounts
SET mount_provider_class = 'OC\\\\Files\\\\Mount\\\\LocalHomeMountProvider'
WHERE mount_provider_class = 'OC\\\\Files\\\\Mount\\\\ObjectHomeMountProvider';
EOF

mysql_user='root'
mariadb --user="$mysql_user" --password --database=nextcloud < /tmp/update-database.sql

# sanity check: there should only be one entry starting with `local::`
mariadb --user="$mysql_user" --password --database=nextcloud \
    --execute="SELECT * FROM nextcloud.oc_storages WHERE id LIKE 'local::%';"

Object-Store-Konfiguration aus der Nextcloud-Config entfernen

Den objectstore-Block aus config.php entfernen und Maintenance-Mode deaktivieren:

$EDITOR /var/www/html/nextcloud/config/config.php

sudo -u apache php /var/www/html/nextcloud/occ maintenance:mode --off
systemctl restart php-fpm
systemctl enable --now nextcloud-jobs.timer nextcloud-scan-files.timer

Anschliessend ist die Instanz wieder im Local-Storage-Modus. Upload, Preview und Sharing testen.

Best Practices

  • Den Bucket-Inhalt niemals von Hand anfassen. Dateinamen existieren nur in der Datenbank; im Bucket liegen die Files unter Keys der Form urn:oid:<fileid>. Ein manuelles Umbenennen, Löschen oder Kopieren zerstört die Konsistenz zwischen Datenbank und Storage.

  • Backups müssen Datenbank und Object Store gemeinsam und zeitnah sichern. Auseinanderlaufende Stände führen zu verwaisten Einträgen in oc_filecache (Datei in DB, nicht im Bucket) oder verwaisten Objects (Datei im Bucket, nicht in DB).

  • Keine S3-Lifecycle-Rules oder Swift-Expiry-Policies auf dem Bucket aktivieren, die Nextcloud nicht kennt. Nextcloud verwaltet Versionen, Papierkorb und Previews selbst in der Datenbank; ein „automatisches Aufräumen“ im Bucket entkoppelt die beiden Sichten.

  • Der Preview-Generator läuft auch bei Object Storage vollständig über das Backend und erzeugt pro Bild mehrere Varianten als eigene Objekte. Auf langsamen oder per-Request abgerechneten Object Stores kann das spürbar kosten - ggf. den Preview-Generator drosseln oder preview_max_* in config.php einschränken.

  • Multi-Bucket-Konfiguration (multibucket true, Bucket-Name mit Prefix pro User) ist für grosse Instanzen möglich, erhöht aber die Komplexität beim Backup und beim Provider-Wechsel. Wer sie einsetzt, dokumentiert Bucket-Verteilung und Prefix-Schema pro Instanz.

  • Umstellen eines bestehenden Local-Storage-Systems auf Object Storage macht alle bestehenden Files unzugänglich, bis die Inhalte migriert sind. Die Migration lässt sich nicht nachträglich „einfach einschalten“ - immer den vollständigen Umzug gemäss obiger Anleitung fahren.