GAS『Service Error: Drive』の原因と対処法
- 作成日 2025.11.06
- その他
Google Apps ScriptからDrive(DriveAppまたは高度なDrive API)を操作した際に、サーバ側で処理できず失敗したときに出る汎用エラー。権限・共有ドライブのロール不備、ID/URLの取り違え、ゴミ箱・削除、MIME不一致、サイズ超過、レート/同時実行超過、一時障害などが主因。入口(誰が、どのAPIで、何を、どの対象に)を確定し、権限整備・入力検証・チャンク処理・指数バックオフ・排他制御で安定化させる。
- 1. エラーの意味と発生条件(まず把握)
- 2. 最短復旧フロー(5手で切り分け)
- 3. DriveAppと高度なDrive APIを区別し、スコープと認可を確認
- 4. ID/URL/MIMEの取り違えを除去(入力検証の基本)
- 5. 共有ドライブと権限(ロール不足は即失敗)
- 6. レート/同時実行/サイズの壁(分割+バックオフで通す)
- 7. フォルダ列挙/ページングの正攻法(巨大フォルダでのタイムアウト回避)
- 8. コピー/エクスポート/変換はサイズ依存(チャンクや分割を前提)
- 9. ゴミ箱/削除/所在の確認(“存在するが取れない”を特定)
- 10. Webアプリ/トリガー:実行主体を合わせて失敗を減らす
- 11. コピーや書込みは冪等化(再試行で重複しない)
- 12. NG→OK早見(クイック修正)
- 13. チェックリスト(上から順に潰す)
エラーの意味と発生条件(まず把握)
・DriveApp/Drive高度サービスいずれでも発生する“サーバ側失敗”の総称。
・発生しやすい操作:大容量コピー/エクスポート、共有設定変更、共有ドライブ間移動、短時間の大量呼び出し、アクセス権のないID参照、ゴミ箱/削除済み参照。
・「たまに失敗→再試行で通る」は一時障害/レート境界、「毎回同じ場所で失敗」は前提不一致(権限/ID/MIME等)の疑いが濃い。
最短復旧フロー(5手で切り分け)
1) どのAPIか:DriveAppか、高度なDrive API(Drive.*)かを判定。
2) 実行主体は誰か:Webアプリの「自分として実行」or「アクセスユーザーとして実行」、トリガー作成者の権限を確認。
3) 対象ファイルは存在/MIME一致/可視か:IDとMIME、ゴミ箱/共有ドライブの所在、ロールを確認。
4) I/Oサイズ/頻度は適正か:大きい操作は分割し、指数バックオフで再試行。
5) 再現性:毎回か時々かで恒久対策(検証/権限整備)と暫定対策(リトライ/スロットリング)を分ける。
DriveAppと高度なDrive APIを区別し、スコープと認可を確認
・DriveApp:簡易API。権限は「このアプリがGoogleドライブへのアクセスを…」の同意で付与。
・Drive(高度サービス):Resources→Advanced Google servicesで有効化+Cloud側API有効化とOAuthスコープ。
・途中で権限を広げたら“新バージョンとしてデプロイ”して再同意が必要(Webアプリ/実行API)。
ID/URL/MIMEの取り違えを除去(入力検証の基本)
function extractFileId(urlOrId) {
const m = String(urlOrId).match(/\/file\/d\/([a-zA-Z0-9-_]+)/) ||
String(urlOrId).match(/[-\w]{25,}/);
return m ? (m[1] || m[0]) : String(urlOrId).trim();
}
function assertSpreadsheet(fileId) {
const mime = DriveApp.getFileById(fileId).getMimeType();
if (mime !== MimeType.GOOGLE_SHEETS) throw new Error('対象はスプレッドシートではありません: ' + mime);
}共有ドライブと権限(ロール不足は即失敗)
・共有ドライブでは「コンテンツ管理者/編集者」以上が編集可。閲覧者/コメント可は大半の書込み不可。
・他人所有/組織外移動/ゴミ箱はDriveApp.getFileById時点や書込み時に失敗。
function whoAmI() {
return {
effective: Session.getEffectiveUser().getEmail(),
active: Session.getActiveUser().getEmail()
};
}レート/同時実行/サイズの壁(分割+バックオフで通す)
function withRetry(fn, tries = 5, base = 400, max = 8000) {
let wait = base;
for (let i = 0; i < tries; i++) {
try { return fn(); }
catch (e) {
const m = String(e && e.message || '');
const transient = /service|internal|backend|rate|quota|timeout|unavailable|exceeded/i.test(m);
if (!transient || i === tries - 1) throw e;
Utilities.sleep(wait + Math.floor(Math.random() * 250)); // ジッタ
wait = Math.min(wait * 2, max);
}
}
}
function withDriveLock(work, waitMs = 20000) {
const lock = LockService.getScriptLock();
if (!lock.tryLock(waitMs)) throw new Error('ロック取得に失敗');
try { return work(); } finally { lock.releaseLock(); }
}フォルダ列挙/ページングの正攻法(巨大フォルダでのタイムアウト回避)
function listFilesPaged(folderId, limit = 500) {
const files = DriveApp.getFolderById(folderId).getFiles();
const out = [];
while (files.hasNext() && out.length < limit) {
const f = withRetry(() => files.next());
out.push([f.getName(), f.getId(), f.getUrl(), f.getSize()]);
}
return out; // 大量ならlimitを刻んで複数回に分ける
}コピー/エクスポート/変換はサイズ依存(チャンクや分割を前提)
// シート→CSVエクスポート(大きい場合はシート分割や範囲指定を検討)
function exportCsv(fileId, gid) {
const url = 'https://docs.google.com/spreadsheets/export?exportFormat=csv' +
'&id=' + encodeURIComponent(fileId) +
(gid != null ? '&gid=' + encodeURIComponent(gid) : '');
const res = withRetry(() => UrlFetchApp.fetch(url, { muteHttpExceptions: true, followRedirects: true, timeout: 15000 }));
if (res.getResponseCode() >= 400) throw new Error('Export failed ' + res.getResponseCode());
return res.getBlob().setName('sheet.csv');
}ゴミ箱/削除/所在の確認(“存在するが取れない”を特定)
function ensureAvailable(fileId) {
try {
const f = DriveApp.getFileById(fileId); // 不可視/削除でここが落ちる
return { name: f.getName(), url: f.getUrl(), size: f.getSize() };
} catch (e) {
throw new Error('ファイルにアクセスできません(ID/共有/ゴミ箱/所有者を確認): ' + e.message);
}
}Webアプリ/トリガー:実行主体を合わせて失敗を減らす
// Webアプリ(自分として実行)で、オーナー権限でDrive操作する例
function doPost(e) {
const id = extractFileId(e?.parameter?.id || '');
const meta = ensureAvailable(id);
return ContentService.createTextOutput(JSON.stringify({ ok: true, meta }))
.setMimeType(ContentService.MimeType.JSON);
}
// 時間トリガー(インストール型)で作成者の認可を使う
function installHourly() {
ScriptApp.newTrigger('batchDrive').timeBased().everyHours(1).create();
}コピーや書込みは冪等化(再試行で重複しない)
function copyOnce(srcId, dstFolderId, nameKey) {
const folder = DriveApp.getFolderById(dstFolderId);
const it = folder.getFilesByName(nameKey);
if (it.hasNext()) return it.next().getId(); // 既存なら再利用
return withRetry(() => DriveApp.getFileById(srcId).makeCopy(nameKey, folder).getId());
}NG→OK早見(クイック修正)
・NG:openByIdにURLを渡す/IDに空白混入 → OK:ID抽出と正規化で厳格に扱う。
・NG:共有ドライブ閲覧者でコピー/書込み → OK:コンテンツ管理者以上へロール昇格 or 「自分として実行」。
・NG:巨大フォルダを一括列挙/コピー → OK:ページング・件数制限・分割実行。
・NG:高頻度連続呼び出し → OK:指数バックオフ+軽いスリープ+排他ロック。
・NG:すべて単発関数で直列実行 → OK:withRetry/withDriveLockの共通化と集中管理。
・NG:毎回同じIDで失敗するが原因不明 → OK:MIME/所在/ゴミ箱/所有者をログに出す診断関数で特定。
チェックリスト(上から順に潰す)
・どのAPI(DriveApp/高度Drive)を使い、必要スコープ/再同意は済んでいるか。
・実行主体(Webアプリ/トリガー/ユーザー)は対象に十分な権限があるか。
・ID/URLの混同はないか、MIMEは期待通りか、ゴミ箱/共有ドライブの所在は正しいか。
・操作対象のサイズ/件数は分割しているか、レート/同時実行対策はあるか。
・一時障害に備えて指数バックオフとスロットリングを実装済みか。
・冪等化して再試行しても重複や二重処理にならないか。
・失敗時のログ(実行主体・ID・MIME・所在・レスポンスコード)を残して後追いできるか。
-
前の記事
docker『Cannot Connect to the Docker Daemon』の原因と対処法 2025.11.06
-
次の記事
docker『Permission Denied』の原因と対処法 2025.11.07
コメントを書く