Path: blob/master/src/packages/database/postgres/blobs.ts
1503 views
import getLogger from "@cocalc/backend/logger";1import type { PostgreSQL } from "./types";2import getPool from "@cocalc/database/pool";3import { callback2 } from "@cocalc/util/async-utils";4import { minutes_ago } from "@cocalc/util/misc";5import { uuidsha1 } from "@cocalc/backend/misc_node";6import { delete_patches } from "./delete-patches";78const logger = getLogger("database:blobs");910/*11archivePatches: Offlines and archives the patches, unless the string is active very recently, in12which case this is a no-op. This removes all patches from the database for this syncstring and13puts them in a single blob, then stores the id of that blob in the archived field of the syncstrings14table.1516TODO: this ignores all syncstrings marked as "huge:true", because the patches are too large.17come up with a better strategy (incremental?) to generate the blobs to avoid the problem.18*/1920export async function archivePatches({21db,22string_id,23compress = "zlib",24level = -1,25cutoff = minutes_ago(30),26}: {27db: PostgreSQL;28string_id: string;29compress?: string;30level?: number;31cutoff?: Date;32}) {33const dbg = (...args) => {34logger.debug("archivePatches", { string_id }, ...args);35};36dbg("get syncstring info");37const pool = getPool();38const { rows: syncstrings } = await pool.query(39"SELECT project_id, archived, last_active, huge FROM syncstrings WHERE string_id=$1",40[string_id],41);42if (syncstrings.length == 0) {43throw Error(`no syncstring with id '${string_id}`);44}45const { project_id, archived, last_active, huge } = syncstrings[0];46if (archived) {47throw Error(48`string_id='#{opts.string_id}' already archived as blob id '${archived}'`,49);50}51if (last_active && last_active >= cutoff) {52dbg("excluding due to cutoff");53return;54}55if (huge) {56dbg("excluding due to being huge");57return;58}59dbg("get patches");60const patches = await exportPatches(string_id);61dbg("create blob from patches");62let blob;63try {64blob = Buffer.from(JSON.stringify({ patches, string_id }));65} catch (err) {66// This might happen if the total length of all patches is too big.67// need to break patches up...68// This is not exactly the end of the world as the entire point of all this is to69// just save some space in the database.70await pool.query("UPDATE syncstrings SET huge=true WHERE string_id=$1", [71string_id,72]);73return;74}75const uuid = uuidsha1(blob);76dbg("save blob", uuid);77await callback2(db.save_blob, {78uuid,79blob,80project_id,81compress,82level,83});84dbg("update syncstring to indicate patches have been archived in a blob");85await pool.query("UPDATE syncstrings SET archived=$1 WHERE string_id=$2", [86uuid,87string_id,88]);89dbg("delete patches");90await delete_patches({ db, string_id });91}9293export async function exportPatches(string_id: string) {94const pool = getPool();95const { rows } = await pool.query(96"SELECT * FROM patches WHERE string_id=$1",97[string_id],98);99return rows;100}101102103104