SaaS tenant data deletion AWS is the job that removes one customer's data and can prove it finished. The customer's rows carry a tenant id. Their objects live under a prefix that is only theirs. Their key is a customer-managed KMS key used for that tenant and no other. Closing the account in the application is not deletion. A row left under that tenant id, an object left under that prefix, or a key that still decrypts are the same incident: the customer is gone and the data is not. Rutagon builds the delete as one workflow with a completion record, on multi-tenant systems we have already shipped, including a production SaaS platform that keeps tenant data in AWS rather than in a pile of per-customer accounts.
This is a build article, not a billing-compliance worksheet. No card numbers belong in the tenant store. If payment data sits in a processor, the delete job does not copy it into the application database to be thorough. The tenant provisioning path is how the key, the prefix, and the partition were created. Deletion is that path run backward, with a record that does not say done until each store says done.
What SaaS tenant data deletion AWS removes for one customer
The partition key is the tenant id. Every item in the operational table includes it, and there is no item for that customer under someone else's key. A global secondary index that omits the tenant id will be queried by mistake during a delete and will either miss rows or scan the neighbors. We do not delete by scanning a table and filtering in application memory. We query the tenant partition and delete those items. Neighbors are a different partition. They are not in the request.
Objects sit in a prefix that contains the tenant id and nothing else. Lifecycle rules that expire the prefix are a backstop, not the completion signal. The job deletes the objects and records the count. A lifecycle that might expire them next week is not a finished delete. The multi-tenant database choices decide how hard this query is. A shared table with a tenant partition is deletable. A shared table with the tenant id buried in a JSON attribute is a project. We do not discover that shape during the customer's last day.
The KMS key is the third store, and it is the one that does not finish in the same minute. AWS KMS will not delete a customer-managed key until a waiting period ends. The key deletion documentation sets that period from 7 to 30 days, with a default of 30. During the wait the key state is pending deletion, cryptographic operations fail, and you can still cancel. After the wait, the key and its metadata are gone, and ciphertext under that key does not decrypt. CloudTrail records both the schedule and the actual deletion. AWS managed keys and AWS owned keys cannot be scheduled this way. The tenant key has to be customer-managed or you do not have a per-tenant delete.
tenant_id: the only customer in this job
items_deleted: count for that partition, repeat until the query is empty
objects_deleted: count under that prefix, repeat until the list is empty
kms_key_state: PendingDeletion until DescribeKey shows the key is gone
completion: closed only when counts are zero and the key is gone
The snippet is the record, not a script full of credentials. We store it in the control table under the tenant id, with the job id and the time each field last changed. Support can answer "is this customer gone" by reading one row. They should not answer by hoping the queue finished.
How the job runs without taking the neighbors with it
Rows go first or objects go first. The order matters less than the fence. The delete calls are scoped to the tenant partition and the tenant prefix. Batch writes that include a key from another tenant are a bug we test for, because a bad builder in a batch call is how you destroy the wrong customer. The test fixture is two tenants, a delete for one, and a read of the other that still returns their item and their object.
KMS is scheduled, not spun until it vanishes. The record moves to pending and stores the deletion date DescribeKey returns. The actual date can run up to a day longer than the period you asked for, which is why we store the describe result instead of adding days on a laptop clock. A completion flag that flips when the schedule call returns is a lie. The customer-visible state can say the delete is in progress. The record stays open. A follow-up check after the waiting period confirms the key is gone, writes that time, and closes the record. If someone cancels deletion during the wait, the key returns to a usable state and the record reopens. We alert on that cancel. It is either a mistake or a request to keep the data, and both need a person.
Caches and search projections are in the job if they can return the customer's data. A search index that keeps the document after the table delete will show the customer in an admin query. We delete by the same tenant id and record that count. Queues and logs are the honest limit. A log line that already shipped to the archive may contain an identifier. We do not promise that a log pipeline rewrites history in this job. We do promise the operational stores: the table, the bucket prefix, the key, and the indexes we built to serve the product. The record lists those stores by name so the promise is specific.
Tokens and sessions for that tenant are revoked in the same job. A session that still works is access, even when the rows are gone. The identity provider's user, if the tenant had one, is disabled and then removed on the same record. We do not leave a login that authenticates into an empty shell and errors in a way that leaks whether the tenant existed.
Idempotency is mandatory. A retry after a timeout must not double-count and must not touch another tenant. The job key is the tenant id. Running it twice when the data is already gone should observe empty counts and leave the KMS state alone if the key is already pending or already gone. Running it twice when the first attempt deleted half the objects should finish the rest. Operators will retry. The workflow has to tolerate that.
What the completion record is for
The record is the artifact you show when the customer asks for confirmation, and the artifact you show when an internal reviewer asks whether offboarding is real. It has the tenant id, the item count, the object count, the key id, the key state, the scheduled deletion time, and the closed time. Closed means the queries return empty and the key is not there. Pending means we told the truth about the waiting period. We do not email "your data is deleted" while DescribeKey still returns pending deletion.
Partial failure stays open. If objects are gone and the table delete throttled, the record shows the object count and the remaining item count. The job continues. Nobody marks the tenant closed to clear a dashboard. A dashboard that counts closed records is fine. A dashboard that counts scheduled jobs as closed is how you report a delete you did not finish.
Full-stack development is the build: the data model that makes one tenant addressable, the workflow that deletes only that tenant, and the record that waits for KMS. Provisioning created the key and the prefix. Deletion is not a manual console session. Console deletes do not write the record, and they are how a second tenant gets caught in a bucket listing someone filtered by eye.
Rutagon has shipped this shape on a production SaaS platform: tenant-scoped rows, tenant-scoped objects, and a key per tenant so a delete is a key schedule rather than a hope that shared encryption hides one customer. Shared encryption with a single account key cannot offer the same finish line. You can delete rows and objects, and you cannot make the old ciphertext undecryptable without destroying every other tenant's data. If the product requires that finish line, the key has to be per tenant from the start. Retrofitting it during a delete request is a migration, and we will say so instead of pretending the schedule call covers ciphertext under a shared key.
The waiting period is also the safety valve KMS insists on. We do not ask for a way around it. We put the period in the customer communication and in the record. Seven days is the shortest the service allows. The default is longer. Pick it in the design, store it on the key schedule, and do not describe the data as gone on the day the request arrived.
Frequently Asked Questions
Why isn't the tenant gone when the KMS schedule call succeeds?
Scheduling starts a waiting period of 7 to 30 days, and the default is 30. Until that period ends, the key exists in a pending state and deletion can be cancelled. The completion record stays open through the wait and closes when the key is actually gone. Telling the customer the delete finished at schedule time ignores how KMS works.
How do you keep from deleting another customer's rows?
The delete query is the tenant partition, and the object delete is the tenant prefix. A batch call is not allowed to carry a key from any other tenant, and we test with two tenants to prove the neighbor still reads back. A table that does not put the tenant id in the primary key is not ready for this job. That is a modeling change, not a careful manual delete.
What if a retry runs after the first delete succeeded?
The job is idempotent on the tenant id. Empty queries stay empty, a key that is already pending is not scheduled a second time, and a key that is already gone stays a closed record. Retries finish partial work. They do not widen the tenant.
Do logs and backups count as deleted in the same record?
The record lists the stores it covers: the operational table, the object prefix, the indexes that serve the product, and the tenant's KMS key. A log archive that already received a line is a different retention decision. We do not mark that archive deleted if this job did not rewrite it. The promise stays as wide as the job and no wider.
Can one shared KMS key satisfy a per-tenant delete?
No. Deleting a shared key makes every tenant's ciphertext unrecoverable. Scheduling deletion of a key that only this tenant used is the finish line for "the key is gone." If the product encrypted everyone under one key, the delete can still remove rows and objects, and the record must say the key is shared and was not destroyed. We would rather split the key before you promise otherwise.
Ask us to build the tenant delete and the completion record. Contact Rutagon at rutagon.com/contact, 907-841-8407, or contact@rutagon.com.