chore: remove invocations

This commit is contained in:
Ben Allfree
2023-11-05 04:20:48 -08:00
parent 098a00c72d
commit f2338f2da5
20 changed files with 14 additions and 251 deletions

View File

@@ -19,7 +19,7 @@
console.log(instanceId)
let isReady = false
$: ({ status, version, secondsThisMonth } = $instance)
$: ({ status, version } = $instance)
assertExists($instance, `Expected instance here`)
const { subdomain } = $instance
@@ -41,9 +41,7 @@
<div class="badge badge-accent badge-outline">
Status: &nbsp;<span class="capitalize">{status}</span>
</div>
<div class="badge badge-accent badge-outline">
Usage: {Math.ceil(secondsThisMonth / 60)} mins
</div>
<div class="badge badge-accent badge-outline">Version: {version}</div>
</div>
</div>

View File

@@ -1,9 +1,8 @@
<script lang="ts">
import ProvisioningStatus from '$components/ProvisioningStatus.svelte'
import AccordionItem from '../../../../components/AccordionItem.svelte'
import { instance } from './store'
$: ({ status, version, secondsThisMonth } = $instance)
$: ({ status, version } = $instance)
</script>
<div class="card card-body bg-base-200">
@@ -13,10 +12,6 @@
Status: <ProvisioningStatus {status} />
</div>
<div>
Usage: {Math.ceil(secondsThisMonth / 60)} mins
</div>
<div>
Version: {version}
</div>

View File

@@ -1,7 +1,6 @@
<script lang="ts">
import { format, subMonths } from 'date-fns'
import { Line } from 'svelte-chartjs'
import { instance } from './store'
import Card from '$components/cards/Card.svelte'
import CardHeader from '$components/cards/CardHeader.svelte'
@@ -26,8 +25,6 @@
CategoryScale,
)
$: ({ secondsThisMonth } = $instance)
// Calculate the last six months
const getLastSixMonths = () => {
let currentDate = new Date()
@@ -67,7 +64,7 @@
pointHoverBorderWidth: 2,
pointRadius: 1,
pointHitRadius: 25,
data: [24, 3, 16, 56, 55, Math.ceil(secondsThisMonth / 60)],
data: [24, 3, 16, 56, 55, Math.ceil(0 / 60)],
},
],
}

View File

@@ -27,9 +27,6 @@
<div class="badge badge-accent badge-outline">
Status: &nbsp;<span class="capitalize">{instance.status}</span>
</div>
<div class="badge badge-accent badge-outline">
Usage: {Math.ceil(instance.secondsThisMonth / 60)} mins
</div>
<div class="badge badge-accent badge-outline">
Version: {instance.version}
</div>

View File

@@ -96,17 +96,14 @@
getRandomNumber(),
getRandomNumber(),
getRandomNumber(),
Math.ceil(instance.secondsThisMonth / 60),
Math.ceil(0 / 60),
],
}
},
)
// Loop through the instance list again, and create a "total usage" entry
const totalUsageAmount = allInstancesArray.reduce(
(total, instance) => total + instance.secondsThisMonth,
0,
)
const totalUsageAmount = allInstancesArray.reduce((total) => total + 0, 0)
// Add up the individual instance usages and the total usage
const allChartData: any = [

View File

@@ -68,7 +68,7 @@ Use the FTP feature to transfer all your data.
The PocketHost service is free until we reach v1.0.
At that point, we will likely introduce a free tier based on "run time" minutes per month.
At that point, we will likely introduce a paid tier while keeping a generous free tier.
### What is the pockethost.io free tier and restrictions?
@@ -78,11 +78,6 @@ pockethost.io offers a free tier to everyone. The free tier includes:
- Unlimited storage
- 1 project
- Connect to your instance from `your-instance.pockethost.io`
- 100 CPU minutes per month
A "CPU minute" is one minute of your `pocketbase` instance running on our system. PocketHost shuts down idle instances to conserve resources. In practice, about 90% of projects use fewer than 100 minutes of actual CPU time per month.
The free tier is less suitable for realtime applications that require the `pocketbase` instance to run continuously.
### What paid plans are there?
@@ -90,15 +85,9 @@ pockethost.io offers a one-size-fits-all paid plan. The paid plan includes:
- Unlimited bandwidth
- Unlimited storage
- Unlimited CPU minutes
- Unlimited projects
- Connect to your instance from `your-instance.pockethost.io` or a custom domain of your choice
- Priority support
### What happens when I reach my minutes / rate / something?
Your instance will be placed into [Maintenance Mode](/docs/usage/maintenance/) until the problem is corrected.
### Are we allowed to have multiple projects running on Pockethost? How many instances can I create?
YES! That is exactly the point of PocketHost. Provision as many PocketBase instances as you desire.

View File

@@ -25,12 +25,6 @@ Instances are placed in hibernation after 5 seconds of idle time.
_Note: Usage Metering is not active until PocketHost reaches v1.0. There is no planned timeline for when or if PocketHost will reach v1.0_
The free tier of PocketHost provides 100 _active minutes_ per month.
![View of an instance showing the total usage](/docs/instance-usage-screenshot.png)
> Because an instance stays active for a minimum of only 5 seconds per activation, 100 minutes of real, active usage is actually quite a bit. After you use your 100 minutes, you can either pay for more usage minutes, or PocketHost will move your instance to the pool of stand-by instances that get served after everyone else. Again, in practice, you will likely not even notice the difference. But if you do, there is always a paid option.
## Instance Versioning
By default, your instance will use the latest major+minor release of PocketBase. The PocketBase version is locked when your instance is created. We use [semver](https://semver.org/) ([npm package](https://docs.npmjs.com/cli/v6/using-npm/semver)) to determine the version range that should be allowed for your instance. When your instance is launched, it will use the latest matching version.

View File

@@ -30,7 +30,7 @@ layout: layouts/home.njk
<ul role="list" class="mt-8 space-y-3 text-sm leading-6 sm:mt-10 text-gray-600">
<li class="flex items-center gap-x-3">
<i class="fa-regular fa-check text-primary"></i>
5 instances
2 projects (instances)
</li>
<li class="flex items-center gap-x-3">
<i class="fa-regular fa-check text-primary"></i>
@@ -38,7 +38,7 @@ layout: layouts/home.njk
</li>
<li class="flex items-center gap-x-3">
<i class="fa-regular fa-check text-primary"></i>
100 minutes / month
1 gb transfer / month
</li>
<li class="flex items-center gap-x-3">
<i class="fa-regular fa-check text-primary"></i>
@@ -72,15 +72,15 @@ layout: layouts/home.njk
</li>
<li class="flex items-center gap-x-3">
<i class="fa-regular fa-check text-primary"></i>
10gb of storage
Unlimited storage
</li>
<li class="flex items-center gap-x-3">
<i class="fa-regular fa-check text-primary"></i>
Unlimited minutes
Unlimited transfer
</li>
<li class="flex items-center gap-x-3">
<i class="fa-regular fa-check text-primary"></i>
Priority help on the discord channel
Dedicated Discord channel for support
</li>
</ul>

View File

@@ -1,7 +1,6 @@
import {
InstanceFields,
InstanceId,
InvocationFields,
IoCManager,
mkSingleton,
UserFields,
@@ -117,8 +116,6 @@ export type MothershipProvider = {
subdomain: InstanceFields['subdomain'],
): Promise<[InstanceFields, UserFields] | []>
updateInstance(id: InstanceId, fields: Partial<InstanceFields>): Promise<void>
createInvocation(instance: InstanceFields): Promise<InvocationFields>
finalizeInvocation(invocation: InvocationFields): Promise<void>
}
type UnsubFunc = () => void

View File

@@ -1,9 +0,0 @@
/// <reference path="../types/types.d.ts" />
// onAfterBootstrap((e) => {
// $app
// .dao()
// .db()
// .newQuery(`update invocations set endedAt=datetime('now') where endedAt=''`)
// .execute()
// })

View File

@@ -203,9 +203,6 @@ export const instanceService = mkSingleton(
client.updateInstanceStatus,
)
const updateInstance = clientLimiter.wrap(client.updateInstance)
const createInvocation = clientLimiter.wrap(client.createInvocation)
const pingInvocation = clientLimiter.wrap(client.pingInvocation)
const finalizeInvocation = clientLimiter.wrap(client.finalizeInvocation)
/*
Handle async setup
@@ -297,15 +294,6 @@ export const instanceService = mkSingleton(
dbg(`killed ${id}`)
})
/*
Create the invocation record
*/
healthyGuard()
const invocation = await createInvocation(instance, pid)
shutdownManager.add(async () => {
await finalizeInvocation(invocation).catch(error)
})
/*
API state, timers, etc
*/
@@ -343,19 +331,6 @@ export const instanceService = mkSingleton(
}, RECHECK_TTL)
}
{
tm.repeat(
() =>
pingInvocation(invocation)
.then(() => true)
.catch((e) => {
warn(`_pingInvocation failed with ${e}`)
return true
}),
1000,
)
}
dbg(`${internalUrl} is running`)
status = InstanceApiStatus.Healthy
healthyGuard()

View File

@@ -8,7 +8,6 @@ import { InstanceLogger, PortService } from '$services'
import {
createCleanupManager,
createTimerManager,
InvocationPid,
LoggerService,
mkSingleton,
SingletonBaseConfig,
@@ -44,7 +43,7 @@ export type PocketbaseServiceConfig = SingletonBaseConfig & {}
export type PocketbaseProcess = {
url: string
pid: () => InvocationPid
pid: () => string
kill: () => Promise<void>
exitCode: Promise<number | null>
}

View File

@@ -1,61 +0,0 @@
import {
InstanceFields,
InvocationFields,
InvocationPid,
pocketNow,
} from '$shared'
import { InstanceApi } from './InstanceMIxin'
import { MixinContext } from './PbClient'
export const createInvocationMixin = (
context: MixinContext,
instanceApi: InstanceApi,
) => {
const { logger } = context
const { dbg } = logger.create('InvocationMixin')
const { client } = context
const createInvocation = async (
instance: InstanceFields,
pid: InvocationPid,
) => {
const init: Partial<InvocationFields> = {
uid: instance.uid,
startedAt: pocketNow(),
instanceId: instance.id,
totalSeconds: 0,
}
const _inv = await client
.collection('invocations')
.create<InvocationFields>(init)
return _inv
}
const pingInvocation = async (invocation: InvocationFields) => {
const totalSeconds = (+new Date() - Date.parse(invocation.startedAt)) / 1000
const toUpdate: Partial<InvocationFields> = {
totalSeconds,
}
const _inv = await client
.collection('invocations')
.update<InvocationFields>(invocation.id, toUpdate)
return _inv
}
const finalizeInvocation = async (invocation: InvocationFields) => {
dbg('finalizing')
const totalSeconds = (+new Date() - Date.parse(invocation.startedAt)) / 1000
const toUpdate: Partial<InvocationFields> = {
endedAt: pocketNow(),
totalSeconds,
}
dbg({ toUpdate })
const _inv = await client
.collection('invocations')
.update<InvocationFields>(invocation.id, toUpdate)
return _inv
}
return { finalizeInvocation, pingInvocation, createInvocation }
}

View File

@@ -3,7 +3,6 @@ import { Logger, LoggerService } from '$shared'
import { Knex } from 'knex'
import { default as PocketBase, default as pocketbaseEs } from 'pocketbase'
import { createInstanceMixin } from './InstanceMIxin'
import { createInvocationMixin } from './InvocationMixin'
import { createRawPbClient } from './RawPbClient'
export type PocketbaseClientApi = ReturnType<typeof createPbClient>
@@ -37,7 +36,6 @@ export const createPbClient = (url: string) => {
const context: MixinContext = { client, rawDb, logger: _clientLogger }
const instanceApi = createInstanceMixin(context)
const invocationApi = createInvocationMixin(context, instanceApi)
const api = {
client,
@@ -46,7 +44,6 @@ export const createPbClient = (url: string) => {
createFirstAdmin,
adminAuthViaEmail,
...instanceApi,
...invocationApi,
}
return api

View File

@@ -1,33 +0,0 @@
import { BaseFields, InstanceId, RecordId } from './types'
export enum BackupStatus {
Queued = 'queued',
Running = 'running',
FinishedSuccess = 'finished-success',
FinishedError = 'finished-error',
}
export type BackupRecordId = RecordId
export type BackupFields = BaseFields & {
instanceId: InstanceId
status: BackupStatus
message: string
bytes: number
version: string
progress: {
[_: string]: number
}
}
export type BackupFields_Create = Pick<
BackupFields,
'instanceId' | 'status' | 'version'
>
export type BackupFields_Update = Partial<
Pick<
BackupFields,
'instanceId' | 'status' | 'bytes' | 'message' | 'version' | 'progress'
>
>

View File

@@ -1,4 +1,4 @@
import { BaseFields, RecordId, Seconds, Subdomain, UserId } from './types'
import { BaseFields, RecordId, Subdomain, UserId } from './types'
export type VersionId = string
@@ -24,7 +24,6 @@ export type InstanceFields = BaseFields & {
uid: UserId
status: InstanceStatus
version: VersionId
secondsThisMonth: Seconds
secrets: InstanceSecretCollection | null
maintenance: boolean
}

View File

@@ -1,12 +0,0 @@
import { BaseFields, IsoDate, RecordId } from './types'
export const INVOCATION_COLLECTION = 'invocations'
export type InvocationPid = string
export type InvocationFields = BaseFields & {
uid: RecordId
instanceId: RecordId
startedAt: IsoDate
endedAt: IsoDate
totalSeconds: number
}

View File

@@ -1,7 +1,5 @@
export * from './Backup'
export * from './Instance'
export * from './InstanceLog'
export * from './Invocation'
export * from './Rest'
export * from './User'
export * from './types'

View File

@@ -1,68 +1,15 @@
import { clientService } from '$services'
import {
INSTANCE_COLLECTION,
INVOCATION_COLLECTION,
InstanceFields,
InvocationFields,
LoggerService,
singletonAsyncExecutionGuard,
} from '$shared'
import Bottleneck from 'bottleneck'
import { ClientResponseError } from 'pocketbase'
export const deleteInvocation = singletonAsyncExecutionGuard(
async (invocation: InvocationFields) => {
const { client } = await clientService()
await client.client.collection(INVOCATION_COLLECTION).delete(invocation.id)
},
(invocation) => `deleteInvocation:${invocation.id}`,
)
export const deleteInvocationsForInstance = singletonAsyncExecutionGuard(
async (instance: InstanceFields) => {
const { client } = await clientService()
const { dbg, error } = LoggerService()
.create(`deleteInvocationsForInstance`)
.breadcrumb(instance.id)
const { id } = instance
while (true) {
try {
console.log(`Deleting invocations for ${id}`)
const invocation = await client.client
.collection(INVOCATION_COLLECTION)
.getFirstListItem<InvocationFields>(`instanceId = '${id}'`)
console.log(`Deleting invocation ${invocation.id}`)
await client.client
.collection(INVOCATION_COLLECTION)
.delete(invocation.id)
console.log(`Invocation deleted ${id}`)
} catch (e) {
if (e instanceof ClientResponseError) {
if (e.status === 404) {
dbg(`No more invocations`)
return
}
}
error(e)
break
}
}
},
(instance) => `deleteInvocationsForInstance:${instance.id}`,
)
export const deleteInstance = singletonAsyncExecutionGuard(
async (instance: InstanceFields) => {
const { client } = await clientService()
const { id } = instance
await deleteInvocationsForInstance(instance).catch((e) => {
console.error(
`deleteInvocationsForInstance error`,
JSON.stringify(e, null, 2),
)
throw e
})
console.log(`Invocations deleted for ${id}`)
await client.client
.collection(INSTANCE_COLLECTION)

View File

@@ -23,7 +23,6 @@ const _unsafe_createInstance = async (context: ContextBase) => {
uid: shuffle(users).pop()!.id,
status: InstanceStatus.Idle,
version: `~0.${random(1, 16)}.0`,
secondsThisMonth: 0,
secrets: {},
maintenance: false,
})