- Project Purpose: NEXL ML is a data collection platform designed to gather human feedback on a variety of query types. It enables users to conduct active learning experiments while tracking experimental data and other key metrics.
- Scope: This documentation outlines the available query types and their usage, including how to modify input parameters. It also provides guidance on deploying the application, starting and testing an experiment, and managing a running experiment by performing actions such as retiring or unretiring an experiment, and monitoring embedding calculations and server performance metrics.
-
Overview: Given
$A$ number of items, participants choose$B < A$ out and rank them based on some anchor item. -
Query Interface:
- The red box displays the full pool of items.
- The green box is where the
$B$ items that a participant intends to rank are placed. - Clicking on an item in the red box transfers it to the green box, while clicking on an item in the green box returns it to the red box.
- To arrange the items, simply drag and drop them within the green box.
- A participant can only submit their response when exactly
$B$ items are in the green box. - Exception: on trap questions (see below), participants may move one or more of the option cards into the green box — moving just their chosen answer is enough.
-
Dynamic Sampling Algorithm:
Each query is generated dynamically by some algorithm.
- Random Sampling: Implements a basic approach by randomly selecting
$A+1$ items from all user-provided targets.- Users can modify the input values for
$A$ ,$B$ , and the set of items to be ranked (see templateNEXT/local/template/ArankB-init.yamlfor all adjustable parameters).
- Users can modify the input values for
- Info-Tuple Sampling: Dynamically updates an embedding based on responses received in real time and selects the tuple of items expected to yield the highest information gain as the subsequent query.
- In addition to the configurable options mentioned above, users can adjust a range of parameters unique to Info-Tuple Sampling, including the number of burn-in iterations, total iterations, down-sampling rate, and more (see template
NEXT/local/template/ArankB-init.yamlfor comprehensive configuration details and explanations for each parameter).
- In addition to the configurable options mentioned above, users can adjust a range of parameters unique to Info-Tuple Sampling, including the number of burn-in iterations, total iterations, down-sampling rate, and more (see template
- Random Sampling: Implements a basic approach by randomly selecting
-
Trap Question Mechanism:
A Rank B includes a trap question system to ensure data quality by identifying inattentive or dishonest participants.
- Purpose: Trap questions are designed to catch participants who are not paying attention or are providing random responses, helping maintain the quality of collected data.
- Implementation: Trap questions are stored in the target set after the regular targets. They are rendered to be visually indistinguishable from a real query: the attention-check instruction appears inside the same purple "Target" card that normally holds the anchor item, and the answer options appear as ordinary cards in the red box (shuffled on every load). The participant moves one or more option cards into the green box — just their chosen answer, or a fuller ranking, both are accepted. Users can configure frequency, tolerance thresholds, and expulsion policies.
-
Scoring: the answer counts as correct only if the correct option is the first (leftmost) card in the green box; any other cards and their order do not matter. In the target set, the first comma-separated option in
primary_descriptionis the correct answer andalt_descriptionis the question shown in the purple card. - Example: a trap question might show "Choose the option with the word positive in it." in the purple Target card with option cards "Positive, Negativity, War, Peace" — moving "Positive" into the green box (alone, or ranked first among others) passes.
-
Configuration: Users can modify trap question settings including enabling/disabling traps, setting frequency, tolerance levels, and expulsion policies. See template
NEXT/local/template/ARankB-InfoTuple.yamlfor detailed parameter explanations and configuration options.
-
Overview: Given
$n$ words, participants need to choose the most positive word(Rank One)/ rank from most positive to most negative(Rank N). -
Query Interface for Rank One:
- The central box displays the full pool of words.
- Clicking on a word will highlight it to indicate choice.
-
Query Interface for Rank N:
- The central box displays the full pool of words.
- Clicking on a word will highlight it to indicate choice.
- A participant can only submit their response when exactly one word is clicked.
-
Static Sampling Algorithm:
- Read CSV: Given a CSV file where each row represents the content of one query. Truncate the first number_of_queries rows off and naively present them as query.
- A specific way of implementing static sampling explained previously.
- Read CSV: Given a CSV file where each row represents the content of one query. Truncate the first number_of_queries rows off and naively present them as query.
- Overview: Given a reference item and a set of target items along a perceptual continuum, participants adjust a slider to match the reference item with the most similar/dissimilar target item depending on the instruction. PAQ supports multiple media types including colors, images, text.
- Query Interface:
- The left area displays the reference item (color, image, text, audio, or video)
- The right area shows the target item that changes as participants move the slider
- A horizontal slider allows participants to toggle and adjust the target item at some predefined range
- The number of queries along the slider can be determined by the developer. In general, the number can be taken as 100 for color vision to ensure numerical precision accuracy
- Participants submit their response by clicking the submit button
- Supported Query Types:
- Color PAQ: Participants match a reference color by adjusting through a continuously changing color path
- Image PAQ: Participants match a reference image by adjusting through morphed image transformations
- Text PAQ: Participants match a reference text by adjusting through text variations
- Algorithms:
- ColorVision: Generates color paths in xyY color space with directional sampling. For more information about PAQ, see the paper: https://arxiv.org/abs/2309.04626.
- Users can configure reference colors, directional vectors, number of ticks, and tick visibility
- ImageTransformation: Creates image morphing between start and end images
- Users can configure start, reference, and end images, number of ticks, and tick visibility
- ColorVision: Generates color paths in xyY color space with directional sampling. For more information about PAQ, see the paper: https://arxiv.org/abs/2309.04626.
- Dynamic Sampling Algorithm:
- DynamicPAQ: Active learning in PAQ queries construction that dynamically selects items for each query rather than using predefined sequences
- Randomly selects start, reference, and end items from the available target set
- Currently supports ImageTransformation algorithm
- Start, Reference, and End Items: These are the key components of a PAQ query. The start item represents the beginning of the perceptual continuum, the reference item is what participants try to match, and the end item represents the end of the continuum. For example, in a color matching task, the start item might be a blue color, the reference item could be a specific shade of purple, and the end item might be a red color. The available target set contains all possible items that can be used as start, reference, or end items in the experiment.
- DynamicPAQ: Active learning in PAQ queries construction that dynamically selects items for each query rather than using predefined sequences
- Configuration: Users can modify parameters including reference items, directional vectors (for ColorVision), start/end items (for ImageTransformation), tick count, tick visibility, and query type (see templates
NEXT/local/template/PAQ-ColorVision.yamlandNEXT/local/template/PAQ-ImageTransformation.yamlfor configuration details).
- Overview: Given an item triplet. Participants need to choose one item out of two given an anchor.
- Query Interface:
- The top box displays the anchor item.
- Clicking one of the item on the bottom to suggest preference.
- Next query will automatically load after clicking.
- Sampling Algorithm:
- CrowdKernel
- RandomSampling
- STE
- UncertaintySampling
- ValidationSampling
- User can decide what algorithms to include with what proportion. Refer to
NEXT/local/PBS-init.yamlfor more details.
- User can decide what algorithms to include with what proportion. Refer to
- Results Overview: All experiment-related input, whether provided by the user or generated by the system, is accessible at all times. Additionally, data such as embeddings, participant counts, and received responses can be monitored continuously, although the update frequency may vary based on the algorithm.
- Performance Metrics: The performance metrics available to the user include an algorithmic timing plot for each type of request, which details how long the NEXT system took to respond to a sequence of requests, as well as a histogram that presents client-side or participant-side timing data, among other information.
This section describes how to set up an AWS instance to host the NEXT ML platform. Instructions for how to run the system locally can be found in the README.md of the local directory in the NEXT github repository. We assume that you have an AWS account and are familiar with computer systems, linux, SSH and the terminal.
-
Instance Type - Use a non-burstable type:
c6i.2xlarge(8 cores, 16 GB, ≈$0.34/h) for up to ~10–15 simultaneous participants,c6i.4xlarge(16 cores, ≈$0.68/h) for more;m6i.*if you want 32 GB of RAM. Select Ubuntu 22.04 LTS as the operating system.Avoid the
t2/t3"burstable" families for data collection. They advertise 8 CPUs but only guarantee 40 % of them (≈3.2 CPUs on a t2.2xlarge); the rest is paid for with "CPU credits" that accumulate while the machine idles and run out after a few hours of heavy use — after which AWS throttles the machine to the baseline and every query computation takes 2–3× longer. That is exactly what happened during the Aug 2026 testing: a 25-participant run that passed on a full credit bank failed the next morning when the bank was empty (CAPACITY_REPORT.md). Ac6i.2xlargeis cheaper than at2.2xlargeand never throttles. If you must stay on a t2/t3, enable Unlimited mode or keep concurrency at ≤10, and watch the CPU credit balance graph in the instance's Monitoring tab;local/healthcheck.shandlocal/leak_monitor.shreport the throttling assteal %.Sizing for a collection day (measured Aug 2026, rank-4 config with precompute,
CAPACITY_REPORT.md). On 8 real cores (c6i.2xlarge) the platform stayed fully reliable up to 60 simultaneous participants — no errors, no lost answers, no leaked connections — but each query's wait grows with the crowd, because every query costs ~13 s of CPU and participants share the cores: about 10–15 at a time for a comfortable study, ~25 for tolerable ~15-second waits (p95 ≈ 30 s), 40+ only if a half-minute wait per query is acceptable. So ac6i.2xlargeis enough for 10–15 concurrent participants; move to ac6i.4xlarge(16 cores) if you want ~25 comfortably. Plan your Prolific concurrency from this (release places in batches) rather than from the nominal CPU count. Every failure mode seen in the Aug 2026 study — the connection leak, server errors mislabelled as failed attention checks, no alerting — is handled (§3.2, §3.3, §3.5).Changing the instance type later needs no reinstall: the root disk is an EBS volume that moves with the instance. Stop the instance (not terminate), Actions → Instance settings → Change instance type, start it again; the Elastic IP stays attached. After the start only nginx restarts by itself — bring the rest back with
docker start local_mongodb_1 local_rabbitmq_1 local_rabbitmqredis_1 local_minionredis_1 local_minionworker_1 local_nextbackenddocker_1 local_cadvisor_1, then re-check the worker counts inlocal/docker-compose.yml.preagainst the new core count (DEV_DOCUMENTATION.md §6).
-
Key Pair - In order to SSH into the system, you will need to use a pre-existing key pair that you have on AWS, or create a new one. We recommend creating a new one as a good security practice. Select create key pair and then do the following:
- name it, i.e NEXT
- use ed25519 (more secure)
- select .pem as the file type and download the .pem
- add the key file (i.e NEXT.pem) to your SSH key folder (i.e .ssh)
- change the file permissions for the .pem file to allows access for your local user only
chmod 400 your_key.pem
- Security Group - Since NEXT is a web facing application, you will need to create a security group to allow HTTP/HTTPS and SSH traffic to be routed to your instance. Otherwise, AWS will block requests to those standard ports. If you do not already have a group setup for this, you can create one while creating your instance. Simply:
- Disk Space -
To launch an AWS instance, you will need hard drive space. Select the amount of space you need. The maximum amount of free tier space should suffice for your needs. Once you finish experiments, you can always download the data and remove the Dockers and rebuild your Docker environment. This will clear old data.

Now you are ready to launch your instance. Simply click Launch instance. Wait for all the checks to pass and then you are ready to SSH into the system. Make sure to take note of the public dns address and the public ip under instance information. You will need them for SSH and nginx respectively. Note: We have included a web server (nginx) into the Docker environment so that you do not need to set up the reverse proxy yourself. You will only need to add the ip address in the nginx.conf file before spinning up the dockers.
- SSH -
Once you have set up your instance (from scratch or from the AMI) and all checks have passed, you can now SSH into your system. All system configuration and experiment launching can be handled from the terminal. Make sure to use the ubuntu user and the .pem file as your identity file when SSHing in. The command will look something like this:
ssh -i /Users/gburdell/Downloads/"students_dev.pem" ubuntu@ec2-44-223-248-38.compute-1.amazonaws.com
This section describes how to set up NEXT on the newly created AWS instance. We are assuming that you are running Ubuntu, have git installed and can SSH into the system. We suggest using tmux to allow for multiple terminals within the single SSH connection and to allow for process persistence if you disconnect from your instance. Sign in to the newly created instance using SSH and continue with the next steps by entering the following commands into the terminal.
-
Clone NEXT Repo - You can clone the latest version of the repository by using the following command:
git clone https://github.com/siplab-gt/NEXT.git
Once you have cloned the repository, change directory to the
NEXT/local/directory and edit thenginx.conffile. You want to replace the value for server_name to the public ip address of your instance. It is currently set to next.localhost. -
Update System and Install Latest Docker - The NEXT platform uses Docker to house and manage all services. You will need to make sure to have the up to date versions and the right libraries installed. To do so follow these steps:
- Add Docker's official GPG key, run the following commands:
sudo apt-get updatesudo apt-get install ca-certificates curlsudo install -m 0755 -d /etc/apt/keyringssudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.ascsudo chmod a+r /etc/apt/keyrings/docker.asc
- Add the repository to Apt sources, run the following commands:
echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \ $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null - Install latest version of Docker and buildx, run this command:
sudo apt-get updatesudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
- Add ubuntu user to the docker group, run:
sudo usermod -aG docker ubuntu
- Restart docker, run:
sudo systemctl restart docker
- Verify installation, run:
sudo docker run hello-world
- Add Docker's official GPG key, run the following commands:
-
Python Environment Setup - In order to launch experiments from the terminal, you will need to have a Python environment setup with the required dependencies installed. Install Python virtual-env with:
sudo apt updatesudo add-apt-repository ppa:deadsnakes/ppasudo apt updatesudo apt install python3.12-fullsudo apt install python3.12-venv
Now you can create virtual environments to install Python packages. Cd into the
NEXT/local/and run the following:python3 -m venv local-venvsource local-venv/bin/activate-
pip install -r requirements.txtYou now have created and activated a Python environment named local-venv. You have also installed all the dependencies needed to launch experiments from the terminal and even run stress tests.
- Configuration File: Navigate to
Next/local/template. There should be one copy of yaml file corresponding to each query type. Follow for more instructions in the template. Please do not remove entry as it may cause error when the experiment launches. - One-step-ahead precompute (A Rank B + InfoTuple): adding
precompute: trueto the experiment config makes the platform compute each participant's next query in the background while they answer the current one, so the ~seconds-long InfoTuple selection overlaps with their thinking time instead of blocking the page. It is off by default, applies per experiment at launch, and changes nothing about the anchor schedule or the collected data — if a background result is not ready in time, the query is computed on the spot exactly as before. Rule of thumb: a participant who spends longer answering than one selection takes to compute sees the next query instantly. SeePRECOMPUTE_REPORT.mdfor the design, verification, and a 30-participant load test with sizing guidance.
-
Launch Experiment
-
Copy and paste your customized yaml file to
Next/local -
Run
docker-compose --version. If this command is not found, run:sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-composesudo chmod +x /usr/local/bin/docker-compose -
Under the same directory, run
./docker_up.shto start the NEXT application. Now, you should be able to access the platform viaInstance_IP_address/home -
Note that you may run into some permission error sometimes due to group membership not getting updated immediately and user may not have permission to access docker's Unix Socket.
-
Run
grep docker /etc/groupand you should see some output similar todocker:x:999:ubuntu. -
Run
newgrp dockerto force group membership update and runid -nG. -
Make sure you see
dockerwithin the list of output. Then running./docker_up.shshould work. -
Troubleshooting:
KeyError: 'ContainerConfig'during startup. docker-compose v1 has a known bug on Docker Engine 25+ that fires whenever it tries to recreate an existing container whose configuration changed — which happens routinely after new git commits (the worker'sGIT_HASHenv changes) or when you start with a different host/IP (the backend's env changes). The failed recreate can also leave a stranded container with a hash-prefixed name (e.g.5df8..._local_minionworker_1). Fix: delete only the stateless containers and start again —docker rm -f local_nextbackenddocker_1 local_minionworker_1 ./docker_up.sh YOUR_PUBLIC_IP(also
docker rm -fany hash-prefixed leftover shown bydocker ps -a). Freshly created containers don't trigger the bug; only recreation does. Neverdocker rmthelocal_mongodb_1container — unlike the backend/worker it owns the anonymous data volume, and removing it orphans your database (see §4.2 and §5). If compose ever insists on recreatingmongodbitself, stop and take a backup first (§5.3). -
Troubleshooting: every request suddenly fails while the machine is idle. If participants report an error screen the moment they start, and the backend log (
docker logs local_nextbackenddocker_1) showsError 99 connecting to rabbitmqredis:6379. Cannot assign requested address, the backend has run out of network ports — historically caused by a Redis connection leak that is now fixed (seeREPRO_REDIS_LEAK.md). Confirm with./leak_monitor.sh 10inlocal/(theclose_waitcolumn should be near zero; thousands means the leak is back) and recover withdocker restart local_nextbackenddocker_1(2 s, no data affected). Participants who were inside the study keep their progress and can continue from their link. -
Next, run
source local-venv/bin/activateto activate a python virtual env. -
Finally, you can launch the experiment with:
python launch.py NAME_OF_YAML_FILE_YOU_CONFIGURED. And to make sure the experiment has successfully launched, go to the home page of NEXT and find Experiment List. Click it and you should be able to find the experiment you just launched by looking at the start date. -
Click on the experiment(its ID will look something like
e7b915a9032b1948750ae5a1a45f47), you will be redirected to the experiment dashboard page. Under Experiment info, you can find the link to open up a query page. Share this link with your experiment participants.
-
- Monitor Data Collected/Metrics Computed in Real Time
- At the same dashboard page, you can spot Experiment data that contains all the experiment-related information including number of participants, number of reported answers, application ID, experiment unique ID, embedding(assuming dynamic sampling algorithms are applied), etc. Note: the participant count is the number of distinct entered IDs — a participant who refreshes and re-enters the same ID does not create a second participant.
- Download Participant Data
- At the same dashboard page, you can spot Participant data that contains all the participant-related information including participant ID, response, decision_time, etc (actual content depends on types of query). You can download it in JSON or in CSV format.
- A Rank B CSV column key: one row per answered query with
participant_uid(the entered Prolific ID, prefixed by the experiment UID),anchor/anchor_id(the anchor item),rank_1..rank_Bwith matchingrank_k_idcolumns (the participant's ranking, left to right),target_position_k/position_k_id(what was displayed),isTrap,query_id, and timing fields (response_time, timestamps). Trap rows intentionally have blank ranking columns (their raw answer is a sentinel, not a real ranking). Queries that were served but never answered (e.g. abandoned by a page refresh) are excluded from the CSV but remain in the JSON without atarget_winnerfield. - Trap outcome columns: every answered row carries
trapped(for a trap row:Truemeans the participant answered it wrong; alwaysFalseon normal rows) andnum_trapped_so_far(the participant's running count of wrong traps at that point). Four participant-level columns are repeated on every row:participant_num_trapped,participant_failed_final,participant_traps_seen, andparticipant_traps_answered. The JSON download has the same aggregates under a top-levelparticipant_summarieskey (per participant:num_trapped,participant_failed,num_answers,traps_seen,traps_answered) alongside the unchangedparticipant_responses. The aggregate columns populate for experiments collected before this feature too; the per-rowtrappedfield exists only for data collected after it. (Data collected before Aug 2026 has one gap: for an expelled participant the final wrong trap's outcome appears only in the aggregates; the expelling answer is now recorded on its row like any other.) - Download URLs: the dashboard links use
/api/experiment/<EXP_UID>/participants?zip=1(JSON) and?csv=1&zip=1(CSV).?csv=1withoutzipreturns the raw CSV body directly.
- Tips on Customize Static Sampling Process with Example
- In
Next/local/csvfolder, a example CSV file is provided as well as other simple python scripts that are used to extract information from the CSV file. It serves as an example of how you could transform each query from your source of file to dictionary format in*-init.yaml. Files in this folder extract queries and initialize a Binary Word Sentinement Classification task introduced in section one. Set configs inconfig.yamland launch experiment by runningpython easy_launch.py.
- In
- Monitor System Performance
- It is always a good idea to monitor and test system performance. This can inform you about the needs of your system and about which processes or services are consuming resources.
- Automatic health check and alerts:
local/healthcheck.shprobes the whole request path (nginx → backend → celery → Redis), counts the backend's dead Redis sockets, checks that every container is up and how many 5xx nginx served in the last 5 minutes, and writes one line per run tolocal/health.log. It alerts when the status changes (and every 30 min while not OK) by posting to a Slack/Discord-style webhook, and on the connection-leak signature it restarts the stateless backend container by itself. Install it once withcrontab -e:and put*/5 * * * * /home/ubuntu/NEXT/local/healthcheck.sh >/dev/null 2>&1WEBHOOK_URL=https://hooks.slack.com/...(plus any threshold overrides listed in the script header) in the gitignoredlocal/alert.local.conf. Without a webhook the alerts are still written tohealth.log.tail -3 local/health.logis the quickest way to see how the stack is doing. - Connection health (manual):
cd local && ./leak_monitor.sh 30 > leak.csv &samples the backend's connections to the Redis result store every 30 s (live, dead/close_wait, Redis's own client count, load, memory, nginx 5xx andErrno 99counts). Healthy:close_wait≈ 0 and the live count returns to zero within a minute of participants finishing. Run it during any collection day and glance at the last line now and then (tail -1 local/leak.csv). - Cadvisor allows you to monitor cpu, memory, and disk usage on the system wide level, as well as per process and per container. We have implemented a password protected version for you. The default user and password is admin and password. To change these, simply change the content in the
cadvisor_user.txtandcadvisor_password.txtfiles respectively. The docker environment will use these to set the username and password for cadvisor. To sign into cadvisor, go to this url:instance-public-ipaddress/cadvisor
- Automatic health check and alerts:
- It is always a good idea to monitor and test system performance. This can inform you about the needs of your system and about which processes or services are consuming resources.
-
Before the first query, every participant is shown a popup asking for their Prolific ID. The entered ID becomes their
participant_uid, so it appears on every response row in the downloaded JSON/CSV participant data (see 3.3). -
The popup is pre-filled automatically when the query page URL carries a
participantparameter:http://InstanceIPAddress/query/query_page/query_page/EXP_UID?participant=PROLIFIC_ID -
Recommended funnel setup (Prolific → Qualtrics → NEXT):
- Set the Prolific study URL to your Qualtrics survey with the ID appended:
https://your-qualtrics-survey-url?PROLIFIC_PID={{%PROLIFIC_PID%}} - In the Qualtrics Survey Flow, add an Embedded Data element named
PROLIFIC_PID(its value is taken from the URL parameter automatically). - Set the Qualtrics End-of-Survey redirect to:
http://InstanceIPAddress/query/query_page/query_page/EXP_UID?participant=${e://Field/PROLIFIC_PID} - If the parameter is ever missing, the popup simply shows an empty box and the participant types their ID by hand — nothing breaks.
- Set the Prolific study URL to your Qualtrics survey with the ID appended:
-
Analysis note: the exported
participant_uidis prefixed with the experiment UID (i.e.EXPUID_PROLIFICID). Strip the prefix (or match by suffix) when joining against Qualtrics/Prolific records. -
Refresh behavior: if a participant accidentally refreshes mid-session, re-entering the same ID (pre-filled automatically when the URL parameter is present) resumes their progress — a refresh costs zero queries: all prior answers are kept, and the unanswered query that was on screen is re-served at the same position, so the participant still answers the full configured number of queries. A participant who reloads the page after finishing is taken directly to the completion (debrief) screen instead of being served extra queries. The same applies after the "Technical problem" exit (§3.5): reopening the link continues the study where it stopped.
-
Only the main query page (
/query/query_page/query_page/...) has this feature. Do not send participants toquery_page_popup.
The query page has three exits, each with its own text and link in the experiment YAML, so a Prolific submission code tells you exactly what happened:
| Exit | When | YAML text / link | Use a Prolific code meaning |
|---|---|---|---|
| Success | all num_tries queries answered |
debrief / debrief_link |
completed |
| Attention-check failure | the participant missed enough trap questions to be expelled (tolerance × num_trap_questions) |
debrief_fail / debrief_link_fail |
failed attention checks |
| Technical problem | the server could not be reached after retry_attempts automatic retries |
debrief_error / debrief_link_error |
technical issue (do not reuse the failure code) |
What the participant sees in each case (test experiment; the links carry placeholder codes):
A failed server call — the page retries automatically; the participant can also press "Retry now".
Retries exhausted — the separate "Technical problem" screen with the technical-issue link. Never the fail code.
A genuine attention-check failure — the only way to reach the fail link.
Completion — the success link.
- A failed server call (timeout, 5xx, nginx error page) is never shown as an attention-check failure. The page shows a "Connection problem — retrying in N s (attempt k of
retry_attempts)" banner with a Retry now button and retries on its own with growing delays (5, 15, 30 s). Progress is kept on the server, so a participant who lands on the technical exit can re-open their link later and continue where they left off. Each attempt waits up to 2 minutes for a hanging server, so the technical exit appears after ~50 s if the server is down and up to ~8 minutes if it is merely hanging. - An interrupted answer is never re-sent: recovery re-requests the query, and the server re-serves the one that was on screen (or the next one if the answer did get through), so nothing is double-counted.
- Create three completion codes on Prolific and put them in the gitignored
local/prolific_codes.local.txt(SUCCESS_CODE=,FAILURE_CODE=,TECHNICAL_CODE=), then runcd local && ./make_live.shto generate the*_live.yamllaunch configs from the tracked templates — re-run it whenever a template changes. Review a "technical issue" submission by checking the participant's progress in the export rather than treating it as a failure. - These screens are exercised automatically by
local/test_query_page_retry.py(DEV_DOCUMENTATION.md §6.5) — run it after any change to the query page.
- At the experiment list page on NEXT
http://InstanceIPAddress/dashboard/experiment_list, check the correct experiment and click "retire" or "unretire".
- Go to
Next/local. - Run
docker-compose stopto stop all the running containers. - WARNING — never run
docker-compose downand never rundocker volume prune. The MongoDB data lives in an anonymous Docker volume:downdeletes the containers and orphans that volume, so the next./docker_up.shstarts with an empty database while your old data sits stranded in a dangling volume (this has happened on this instance — see 5.5 for recovery)../docker_up.shitself usesstop+up, which is safe. - Always use
docker-compose(v1, with the hyphen), notdocker compose(v2). v2 uses a different project naming scheme, attaches fresh empty volumes, and the database will silently appear empty.
- Go to
EC2 > Instanceswebpage. - Check the current instance in the list.
- Find the
Instance stateradio button on the top and choose Stop instance.
- All experiment data is in MongoDB inside the
local_mongodb_1container (MongoDB 7.0 — usemongosh; the legacymongoshell does not exist in this image). - Database
app_data: per-app collections (e.g.ARankB:queries— one document per query including the answer andparticipant_uid,ARankB:participants,ARankB:experiments,ARankB:algorithms— algorithm/model state,ARankB:dashboard,ARankB:other), plus the globalexperiments_admin(experiment index) andtargets(target sets, re-inserted on every launch). - Database
logs:<app>:APP-EXCEPTION,<app>:ALG-DURATION,<app>:ALG-EVALUATION(these grow fastest). - The database files live in an anonymous Docker volume mounted at
/data/db. Redis and RabbitMQ hold only transient task state, andlocal/media/is static files — neither ever needs cleaning.
# collection counts and sizes
docker exec local_mongodb_1 mongosh app_data --quiet --eval 'db.getCollectionNames().forEach(c=>print(c, db[c].countDocuments({}), (db[c].stats().storageSize/1048576).toFixed(1)+"MB"))'
# list experiments, newest first
docker exec local_mongodb_1 mongosh app_data --quiet --eval 'db.experiments_admin.find({},{exp_uid:1,app_id:1,start_date:1}).sort({start_date:-1}).forEach(printjson)'
The dashboard's database "Download" button and the S3 backup scripts are broken legacy code — do not rely on them. The reliable route (the dump lands on the host at NEXT/local/backup_YYYY-MM-DD/):
docker exec local_mongodb_1 mongodump --host 127.0.0.1 --port 27017 --out /next_backend/local/backup_$(date +%F)
Per-experiment participant data can also always be downloaded from the dashboard as JSON/CSV (see 3.3).
There is no working delete function in the app ("retire" only hides an experiment from the dashboard — the data stays). Cleaning is manual:
# delete ONE experiment's data (set the app name and experiment UID first)
docker exec local_mongodb_1 mongosh app_data --quiet --eval '
var a="ARankB", u="EXP_UID_HERE";
["queries","participants","experiments","algorithms","dashboard","other"].forEach(function(c){
printjson({coll:a+":"+c, deleted: db[a+":"+c].deleteMany({exp_uid:u}).deletedCount});});
printjson({targets: db.targets.deleteMany({exp_uid:u}).deletedCount});
printjson({admin: db.experiments_admin.deleteMany({exp_uid:u}).deletedCount});'
docker exec local_mongodb_1 mongosh logs --quiet --eval '
var a="ARankB", u="EXP_UID_HERE";
["APP-EXCEPTION","ALG-DURATION","ALG-EVALUATION"].forEach(function(c){
printjson({coll:a+":"+c, deleted: db[a+":"+c].deleteMany({exp_uid:u}).deletedCount});});'
# OR: full reset (wipes ALL experiments in both databases)
docker exec local_mongodb_1 mongosh --quiet --eval 'db.getSiblingDB("app_data").dropDatabase(); db.getSiblingDB("logs").dropDatabase()'
Indexes are recreated automatically the next time an experiment is launched.
If docker-compose down was ever run (see 4.2), the previous database usually survives in a dangling volume:
docker volume ls -qf dangling=true # candidate volumes
sudo du -sh /var/lib/docker/volumes/VOLUME_HASH/_data # a real mongo dbpath is 100s of MB
Recover by mounting the volume in a throwaway mongod (re-using the image the stack already built), dumping, and copying out:
docker run --rm -d --name mongo_recovery -v VOLUME_HASH:/data/db local_mongodb:latest mongod
docker exec mongo_recovery mongodump --out /tmp/recovered
docker cp mongo_recovery:/tmp/recovered ./recovered_dump
docker stop mongo_recovery # container auto-removes; the volume itself is untouched
Then mongorestore the dump into the live database if desired. As of Aug 2026 this instance has one such orphaned volume (~441 MB, data from Oct 2025–Mar 2026) — do not prune it.
https://mediaspace.gatech.edu/media/NEXT_install_instructions_part_1/1_p9fujklo
https://mediaspace.gatech.edu/media/NEXT_install_instructions_part_2/1_ucyud9f3
PRECOMPUTE_REPORT.md— one-step-ahead query precompute: design, verification, and the first load test (numbers predate the Aug 2026 stability fixes).CAPACITY_REPORT.md— acceptance results after the stability fixes (10 and 25 simultaneous participants) and the plan for a capacity ramp on a larger instance.REPRO_REDIS_LEAK.md— the Aug 2026 outage: how the Redis connection leak was reproduced, what it really was, and the before/after numbers.






