Task: Migrate the website to a new engine
Migrate the website to a new engine
The website is very old, originally built on MODX, then rewritten using custom JS technologies and even the first version of Prisma. As a result, it requires too much resources (2+ GB).
docker stats --no-stream |grep pivkarta
fab6503884d8 docker-pivkartaru-mysql-1 0.47% 531.3MiB / 6.954GiB 7.46% 572MB / 12.1GB 26.7GB / 1.58GB 41
6657aa9c2c22 docker-pivkartaru-front-1 23.59% 405.4MiB / 6.954GiB 5.69% 45.7GB / 91.9GB 40.1GB / 31.6MB 23
202c9196d804 docker-pivkartaru-api-1 3.88% 226.4MiB / 6.954GiB 3.18% 815MB / 547MB 3.71GB / 1.27MB 23
782d45865722 docker-pivkartaru-prisma-1 2.70% 908.3MiB / 6.954GiB 12.75% 500MB / 646MB 8.43GB / 62.9MB 78
For comparison, haih-agent even with n8n consumes no more than a gigabyte (usually around 500 MB).
Ворклоги
Legacy DB Structure: The "Just Connect" Hypothesis is Being Re-evaluated
After running it locally, we examined the actual structure of the existing MySQL database. The scale of the legacy system turned out to be significantly larger than what was visible from the Docker stack alone: 250 tables, approximately 198k rows, and ~82 MiB of data.
Out of the 250 tables, about 150 have the old MODX prefix fSdf12_. This represents a large system layer of MODX itself and Extras installed over the years: ACL/policies, contexts, manager/dashboard, media sources, package transport, lexicon, sessions, users/groups, billing/shop modules, society, SDK/importer, search, monitoring, GeoIP, and other subsystems. A significant portion of these tables are empty or contain literally just a few records, but during legacy analysis, they still look like a potentially significant part of the system.
Separately, there is another historical model layer: dozens of Prisma-style relation tables _...; many of them are currently empty. Traces of sequential migrations and entity duplication are also visible: for example, old and new variants of Place, Beer, User, Photo, comments and junction tables, user backup tables, views, and various storage engines (MyISAM + InnoDB). This is no longer a single domain schema, but multiple generations of architecture living in the same database.
At the same time, the actual domain core of pivkarta.ru is much smaller. The data shows several thousand venues (Place ~3.8k), about 1.6k beer varieties/items Beer, ~13k PlaceBeer relations, photos, users, and relations between venues, beer, metro stations, and users; plus content entities like comments, topics, news/blogs, etc. Which of these are actually needed by the modern version of the product remains to be determined based on actual requirements—the number of records in itself does not prove that a feature needs to be migrated.
This clearly confirms the thesis from the article "MODX pays a huge ongoing architectural price for functionality that most projects barely use": even an empty table does not mean zero architectural cost. If a subsystem exists, working with legacy requires figuring out its model, relations, runtime semantics, and understanding whether it can be safely discarded. In this database, the effect is physically obvious: most of the schema describes engine capabilities and historical experiments rather than today's domain model of pivkarta.ru.
Changing the Direction of the Experiment
In the previous worklog, the first option was the minimally invasive path: keep the existing MySQL as the persistence boundary and connect the new API via Knex. After reviewing the schema, this option no longer looks obviously optimal.
Now, a more promising hypothesis is to create a new clean database with a minimal domain model and migrate only the provenly necessary data into it. Use the old database as a data source and requirements archaeology site, but do not make it the permanent foundation of the new architecture.
This adds work now: the agent will have to figure out which of the 250 tables actually participate in the product, which are MODX/Prisma/Extras technical tables, which represent old versions of the same data, and which features are no longer needed at all. But this is precisely what makes for a good HAIH brownfield test: do not carry over accumulated complexity just because it already exists; restore the real requirements and assemble a minimal new model for them.
The next logical step is to build a map of legacy tables → real capabilities → needed/not needed → new model → migration/verification, and then choose the first vertical scenario and migrate it along with data end-to-end.
Related experiment: haih.site and task /tasks/cmujk0ehv0m20qw0rs14y30bw. Current task: /tasks/cmqqxckd3003zp50up0dmzbld.
Migration Result of pivkarta.ru: Production and SEO/Performance Evidence
Task completed: the new pivkarta.ru has been deployed to production. Further improvements will be handled as separate tasks.
Scope of the Brownfield Migration
The new site is built on top of the evolving HAIH runtime, but for a real legacy project, the engine was significantly upgraded to a full production setup: a unified Express runtime, React Router SSR, GraphQL/Apollo/Pothos, Knex connecting to the existing MySQL, Vite middleware in development, production static serving, Varnish, legacy thumbnail processing via Sharp, ETag/GET/HEAD, and proper server resource cleanup.
The core public structure of the old product has been restored: beers, venues, publications, members, comments, cities, map, and contacts, including historical URLs and entity relationships. Legacy data inventory recorded 1,636 beer entries, 3,797 venues, 600 articles, 64 cities, 3,296 comments, and 26,548 public profiles.
A registry of 73,760 addresses was built for compatibility. A full HTTP crawl recorded passed: 73760 with zero errors on the verified state. Additionally, 11,579 content links and 19,660 asset URLs were checked. Subsequent branch changes were covered by targeted tests, so the old full crawl should not be considered a check of every single commit.
Measurable Production Result
The main work of the current iteration, starting September 28, took 18:01:02 according to timers.
Lighthouse Before/After:
| Metric | Legacy | New |
|---|---|---|
| Performance | 49 | 93 |
| Accessibility | 75 | 100 |
| Best Practices | 74 | 96 |
| SEO | 83 | 100 |
Application runtime according to docker stats:
- legacy
front: 588.4 MiB; - legacy
api: 231.3 MiB; - legacy
prisma: 953.2 MiB; - legacy application layer total: ~1773 MiB (~1.73 GiB);
- new
app: 178.2 MiB.
Thus, application RAM usage decreased by approximately 10 times (~90%), while Lighthouse Performance increased from 49 to 93, and Accessibility and SEO reached 100. It is particularly telling that Prisma 1 alone from the old stack consumed ~953 MiB — more than five times the entire new application runtime.
Main Engineering Conclusion
The migration confirmed the HAIH brownfield hypothesis: to replace a legacy application, it is not necessary to replicate its historical architecture. The practical path turned out to be:
legacy system → restoration of real requirements/capabilities → data and URL contract verification → discarding unnecessary legacy → minimal new architecture → verification → production evidence.
The initial hypothesis of preserving the legacy model as the primary architectural boundary was revised after examining the actual database and its 250 tables. As a result, valuable public data, relationships, and the web contract were preserved, but the accumulated infrastructural complexity of the old system was not replicated.
This distinguishes the result from a standard redesign: a production replacement of a real, long-standing site was achieved with measurable compatibility, a significantly lower runtime cost, and noticeably better web metrics.
Scope of This Task
Within the scope of this task, the migration is considered complete. Deferred write scenarios (authorization, entity creation/editing), further individual redesigns of internal pages, additional production checks, and other improvements should be handled in separate tasks rather than expanding the current one.
New HAIH Experiment: Brownfield on pivkarta.ru
The task was set a long time ago as an upgrade of the old haih-agent, but since then a new HAIH approach has emerged, which we are currently testing in practice at haih.site. Current work on the concept is being conducted in the task "Develop and launch the website", and pivkarta.ru looks like a good next guinea pig for the new engine/approach.
If haih.site is a greenfield experiment on "how quickly to assemble a sufficient architecture from real requirements," then pivkarta.ru can become a brownfield experiment: how quickly an AI agent can analyze an existing product, preserve its real requirements, and gradually replace the outdated architecture without dragging its historical baggage into the new system.
It is important not to start with a total rewrite and a pre-selected "correct stack." The existing MySQL can be left as a working persistence boundary, and for the first vertical slice, connect to it via Knex, build a new typed API on top using Pothos/GraphQL, and use codegen/Apollo + React on the client. That is, migrate primarily the requirements and behavior rather than the old architecture.
The first practical experiment: choose one real user scenario and run it end-to-end through the new system (React → Apollo → GraphQL/Pothos → Knex → existing MySQL), recording the implementation time, issues, verification methods, and resources. The existing baseline of the old system (~2 GB RAM according to current measurements) will allow comparing the result not only by functionality, but also by runtime cost.
This should become the second type of evidence for HAIH after haih.site: not only "how fast AI builds the new," but also how fast it is able to rethink and displace legacy along working verticals.
Legacy baseline launched locally
Managed to get the old pivkarta.ru running locally — Docker really helped preserve reproducibility even years later. At the same time, it became very clear how large a historical layer has accumulated in the current architecture.
Some characteristic baseline components:
- build image:
mhart/alpine-node:10— Node.js 10; - Docker Compose
3.7; prismagraphql/prisma:1.15.3(Prisma 1) as a separate service between the application and the DB;- MySQL
5.7; - separate
frontandapicontainers from a common oldprisma-cms; - separate
pivkarta.ru-2frontend; - phpMyAdmin;
- custom Caddy proxy;
- MailHog;
- separate
rtcmultyconnection; - Coturn with STUN/TURN infrastructure.
That is, this is no longer just a task of updating a few npm packages: we have a solid, real-world brownfield with layers of solutions from different years and a large amount of infrastructure, some of which may turn out to be necessary, and some — historical baggage.
For the HAIH experiment, this is especially useful. We will not mechanically migrate this zoo to modern versions. The old project becomes a source of requirements and behavioral baseline, rather than a specification for the new architecture. For each layer, we need to figure out: what real capability does it provide now, is it needed by pivkarta.ru today, and what minimal verifiable solution satisfies this requirement.
The first planned new vertical slice remains simple: keep the existing MySQL as a legacy persistence boundary and go through React → Apollo/codegen → GraphQL/Pothos → Knex → existing MySQL. This will allow starting to displace the old system with working verticals, without starting with a risky data migration and without carrying over Prisma 1 just because it is present in the legacy.
Related HAIH experiment: haih.site and task «Develop and launch the website». Current pivkarta.ru task: /tasks/cmqqxckd3003zp50up0dmzbld.